posthog/posthog
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·August 1, 2026
Elevated
Design risk in this codebase.
More findings
Verdict
Good primitives, thin follow-through: components are reused correctly but the interaction layer around risk and feedback gets skipped. The delete-with-no-confirmation pattern is the single biggest risk in a codebase that otherwise knows how to build a loading state.
Files Rams reviewed
products/desktop/apps/mobile/src/app/(tabs)/_layout.tsx
products/desktop/apps/mobile/src/app/_layout.tsx
tools/hedgebox-dummy/src/app/files/[id]/page.tsx
tools/hedgebox-dummy/src/app/login/page.tsx
tools/hedgebox-dummy/src/app/mariustechtips/page.tsx
tools/hedgebox-dummy/src/app/page.tsx
tools/hedgebox-dummy/src/app/pricing/page.tsx
tools/hedgebox-dummy/src/app/signup/page.tsx
frontend/src/lib/components/BridgePage/BridgePage.tsx
frontend/src/lib/components/PanelLayout/PanelLayout.tsx
frontend/src/lib/components/SessionTimeline/timeline/items/page.tsx
packages/quill/packages/charts/src/components/Legend/ChartLegendLayout.tsx
products/desktop/packages/ui/src/features/agent-applications/components/AgentDetailLayout.tsx
products/desktop/packages/ui/src/features/agents/components/AgentsTabLayout.tsx
products/desktop/packages/ui/src/features/code-review/components/CloudReviewPage.tsx
products/desktop/packages/ui/src/features/panels/components/PanelLayout.tsx
services/stripe-app/src/views/FullPage.tsx
products/desktop/apps/mobile/src/app/automation/index.tsx
products/desktop/apps/mobile/src/app/mcp-servers/index.tsx
products/subscriptions/frontend/components/Subscriptions/views/ManageSubscriptions.tsx
products/subscriptions/frontend/components/Subscriptions/views/SubscriptionOverviewComponents.tsx
products/subscriptions/frontend/components/Subscriptions/views/TabbedManageSubscriptions.tsx
tools/hedgebox-dummy/src/app/layout.tsx
frontend/src/lib/components/AccessDenied/index.tsx
frontend/src/lib/components/DateDisplay/index.tsx
frontend/src/lib/components/IngestionControls/index.tsx
frontend/src/lib/components/InsightLabel/index.tsx
frontend/src/lib/components/NotFound/index.tsx
frontend/src/lib/components/RichContentEditor/index.tsx
frontend/src/lib/components/TZLabel/index.tsx
UX
products/subscriptions/frontend/components/Subscriptions/views/SubscriptionOverviewComponents.tsx:199
Delete subscription fires instantly with zero confirmation step
In the subscription row's action dropdown, the "Delete subscription" LemonButton (icon={<IconTrash />}, status="danger") calls onClick={onDelete} directly. There is no confirmation dialog, undo affordance, or secondary step between the click and the delete action firing.
Why it matters
A single misclick in a dropdown menu permanently destroys a subscription with no recovery path, which is exactly the kind of irreversible action that needs a guard rail.
Fix
Gate irreversible destructive actions behind an explicit confirmation dialog before calling the destructive handler.
<LemonButton
icon={<IconTrash />}
onClick={onDelete}
data-attr="subscription-list-item-delete"
status="danger"
fullWidth
>
Delete subscription
</LemonButton><LemonButton
icon={<IconTrash />}
onClick={() =>
LemonDialog.open({
title: 'Delete this subscription?',
description: 'This action cannot be undone.',
primaryButton: { children: 'Delete', status: 'danger', onClick: onDelete },
secondaryButton: { children: 'Cancel' },
})
}
data-attr="subscription-list-item-delete"
status="danger"
fullWidth
>
Delete subscription
</LemonButton>products/subscriptions/frontend/components/Subscriptions/views/TabbedManageSubscriptions.tsx:105
Modal list scroll chains into the page behind it at the scroll boundary
The subscription list container uses overflow-y-auto with no overscroll-behavior guard. Scrolling to the top or bottom of this list inside the LemonModal continues scrolling the page underneath.
Why it matters
Scroll chaining inside a modal breaks the expectation that a modal is a self-contained surface, and users can accidentally scroll the background page while trying to browse subscriptions.
Fix
Add overscroll-behavior: contain to scrollable regions inside modals so scroll input never leaks to the page behind them.
<div className="max-h-[60vh] overflow-y-auto flex flex-col gap-2"><div className="max-h-[60vh] overflow-y-auto overscroll-contain flex flex-col gap-2">Accessibility
tools/hedgebox-dummy/src/app/page.tsx:30
User avatar image has no dimensions, causing layout shift on load
The avatar <img src={user.avatar} alt={user.name} /> inside the animate-fade-in-up hero has no explicit width or height attributes; only the parent div is sized via className="w-8 h-8".
Why it matters
Without intrinsic dimensions on the img itself, the browser can't reserve space before the image loads, so the hero content can shift during the entrance animation.
Fix
Set explicit width and height attributes on the image so the browser reserves layout space before load.
<img src={user.avatar} alt={user.name} /><img src={user.avatar} alt={user.name} width={32} height={32} />Get this score on every PR.
Rams reviews each pull request on your repo and posts inline one-click fixes — about a minute per review.
Install Rams freeComponents
products/subscriptions/frontend/components/Subscriptions/views/SubscriptionOverviewComponents.tsx:213
Conditional class built with a template literal instead of the shared cn() helper
The subscription title div uses className={`font-medium truncate ${enabled ? 'text-link' : 'text-muted'}`}, a raw template literal, while conditional classes elsewhere in this file use LemonTag and other primitives that assume class-merging conventions.
Why it matters
Mixing template-literal classes with a cn()-based convention elsewhere in the codebase makes future class merges (e.g. adding a hover or disabled state) error-prone and inconsistent to maintain across files.
Fix
Use the codebase's cn() helper for conditional classNames instead of manual template literals.
<div className={`font-medium truncate ${enabled ? 'text-link' : 'text-muted'}`}>
{subscription.title}
</div><div className={cn('font-medium truncate', enabled ? 'text-link' : 'text-muted')}>
{subscription.title}
</div>Motion
tools/hedgebox-dummy/src/app/page.tsx:165
Footer logo hover animation never fires, dead code shipped as polish
The footer logo mark carries group-hover:scale-105 transition-transform, but no ancestor element in the footer (the flex row at line 164, or any wrapper) has the `group` class applied.
Why it matters
The transition classes ship dead weight and signal an interactive affordance that never triggers, which misleads anyone reading the code into thinking hover feedback exists here.
Fix
Remove unused hover utilities or add the group class to the intended interactive ancestor so the animation actually fires.
<div className="w-7 h-7 bg-primary rounded-lg flex items-center justify-center group-hover:scale-105 transition-transform"><div className="w-7 h-7 bg-primary rounded-lg flex items-center justify-center">Craft
tools/hedgebox-dummy/src/app/page.tsx:108
Three feature cards share one template with zero visual differentiation
The "Fox-proof", "Instant", and "Warm & fuzzy" cards each wrap their emoji in an identical `w-16 h-16 bg-primary/10 rounded-2xl` container with the same card shell, border, and padding, differing only by emoji and copy.
Why it matters
When every card in a comparison section is visually interchangeable, users scan text top to bottom rather than pattern-matching icons, slowing recognition of which benefit maps to which card.
Fix
Vary icon container weight or tint within the existing primary token so each card reads as distinct without introducing a new hue.
<div className="w-16 h-16 bg-primary/10 rounded-2xl flex items-center justify-center mb-6">
<span className="text-3xl">🛡️</span>
</div><div className="w-16 h-16 bg-primary/15 border border-primary/20 rounded-2xl flex items-center justify-center mb-6">
<span className="text-3xl">🛡️</span>
</div>Typography
Color
Spacing
Working well
- The signed-in hero correctly swaps both copy and CTA label ('Go to files') versus the signed-out state ('Get started free'), giving each user state its own clear next action instead of a generic shared CTA bolted onto both.
- Reusing SubscriptionListItem, SubscriptionEmptyState, and LemonModal instead of building bespoke list rows or a custom modal keeps SubscriptionOverviewComponents thin and consistent with the rest of the design system.
- The destination summary correctly sets title={destination.title} to preserve the full value behind a compact truncated label, a pattern the subscription title itself should also follow.
- Tab labels suppress the '(0)' count while loading (rendering plain 'label' instead of '${label} (0)'), avoiding a false empty-state signal before real data arrives.
Scored August 1, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored July 9, 2026: 86/100. This rescore on v0.0.3: 59/100.
This page is an automated design review of posthog/posthog’s UI code: 30 files read against 291 versioned rules covering accessibility, color, typography, spacing, components, UX, motion, and craft. The score is out of 100; confirmed criticals cap it — one at 59, two at 49, three or more at 39.
More design scores
Score your own repo.
Free on public repos, no account. The same engine that scored this page reads your UI code and mints a score page like this one.
Public repos only. The full engine reviews the UI code and mints a public score page — we email you the link too. Already-scored repos open instantly.