triggerdotdev/trigger.dev
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 9, 2026
More findings
Verdict
Solid composable primitives get undermined by sloppy details: hardcoded hex colors, missing labels, and unset logo sizes. The layout wrapper used by every page in the app has no main landmark, making this the highest-leverage fix available.
Files Rams reviewed
apps/webapp/app/components/LoginPageLayout.tsx
apps/webapp/app/components/layout/AppLayout.tsx
apps/webapp/app/components/primitives/SettingsLayout.tsx
apps/webapp/app/components/BackgroundWrapper.tsx
apps/webapp/app/components/BulkActionFilterSummary.tsx
apps/webapp/app/components/DefinitionTooltip.tsx
apps/webapp/app/components/DevPresence.tsx
apps/webapp/app/components/ErrorDisplay.tsx
apps/webapp/app/components/FeatureBadges.tsx
apps/webapp/app/components/Feedback.tsx
apps/webapp/app/components/GitHubLoginButton.tsx
apps/webapp/app/components/GitMetadata.tsx
apps/webapp/app/components/ListPagination.tsx
apps/webapp/app/components/LogLevelTooltipInfo.tsx
apps/webapp/app/components/LogoType.tsx
apps/webapp/app/components/MachineLabelCombo.tsx
apps/webapp/app/components/MachineTooltipInfo.tsx
apps/webapp/app/components/RuntimeIcon.tsx
apps/webapp/app/components/SetupCommands.tsx
apps/webapp/app/components/Shortcuts.tsx
apps/webapp/app/components/TriggerRotatingLogo.tsx
apps/webapp/app/components/WarmStarts.tsx
apps/webapp/app/components/admin/FeatureFlagsDialog.tsx
apps/webapp/app/components/admin/FlagControls.tsx
apps/webapp/app/components/admin/backOffice/MaxProjectsSection.tsx
apps/webapp/app/components/admin/backOffice/RateLimitSection.tsx
apps/webapp/app/components/admin/debugRun.tsx
apps/webapp/app/components/admin/debugTooltip.tsx
apps/webapp/app/components/billing/AnimatedOrgBannerBar.tsx
apps/webapp/app/components/billing/BillingAlertsSection.tsx
Spacing
Table background layer mixes arbitrary inline positioning with no scale reference
The table background div sets `left: "260px"`, `width: "100%"`, `height: "100vh"`, and `backgroundSize: "1200px auto"` all as raw inline style strings rather than Tailwind scale classes or shared constants tied to the sidebar width used elsewhere in this same file.
Why it matters
The `260px` offset here has to stay manually in sync with the `w-[260px]` sidebar width in the two divs above it; there's no shared variable, so a sidebar resize breaks the table background alignment without any compiler warning.
Fix
Derive the offset from one shared constant (e.g. a SIDEBAR_WIDTH value) instead of repeating the literal 260px in a separate inline style.
style={{
left: "260px",
backgroundImage: `url(${blurredDashboardBackgroundTable})`,
width: "100%",
height: "100vh",
backgroundSize: "1200px auto",
backgroundColor: "#101214",
}}style={{
left: SIDEBAR_WIDTH,
backgroundImage: `url(${blurredDashboardBackgroundTable})`,
width: "100%",
height: "100vh",
backgroundSize: "1200px auto",
}}
className="... bg-background"Trusted-by logo row sizes one logo explicitly and leaves five unset
In the "Trusted by developers at" row, `LyftLogo` gets `className="w-11"` but `UnkeyLogo`, `MiddayLogo`, `AppsmithLogo`, `CalComLogo`, and `TldrawLogo` render with no size class at all.
Why it matters
Six logos meant to read as one uniform trust strip end up at whatever default width each component ships with, so the row likely shows uneven logo scale and breaks the alignment the flex/gap layout is trying to create.
Fix
Apply the same sizing class to every logo in a repeated row so the group reads as one consistent unit.
<LyftLogo className="w-11" />
<UnkeyLogo />
<MiddayLogo />
<AppsmithLogo />
<CalComLogo />
<TldrawLogo /><LyftLogo className="w-11" />
<UnkeyLogo className="w-11" />
<MiddayLogo className="w-11" />
<AppsmithLogo className="w-11" />
<CalComLogo className="w-11" />
<TldrawLogo className="w-11" />MainCenteredContainer caps width at max-w-xs regardless of content type
`MainCenteredContainer` hardcodes `max-w-xs` (320px) for its inner wrapper in both the `default` and `onboarding` variants, with no way to widen it besides the passed `className` override fighting the base class.
Why it matters
A fixed 320px cap on a generic centered container used across sign-in, onboarding, and other flows constrains any content wider than a simple form (e.g. a multi-field card or wider copy) and forces every consumer to pass an override className to fight the base, increasing chance of visual drift between pages that remember to override and pages that don't.
Fix
Move the max-width to a sensible default that can be cleanly extended via className without overriding a fixed value.
"mx-auto max-w-xs p-1","mx-auto max-w-xs p-1", // consider widening default or documenting when callers should overrideAccessibility
App-wide layout wrapper has no landmark region for screen readers
`AppContainer`, `MainBody`, and `PageContainer` wrap the entire application in plain `<div>` elements. None of these render `<main>`, `role="main"`, or any landmark. Since this file is the single layout primitive every page passes through, no page built on top of it gets a main landmark unless each page re-adds one individually.
Why it matters
Screen reader users rely on landmark navigation ("skip to main content") to jump past repeated chrome. Without a `<main>` region anywhere in the shared layout, every page in the app forces assistive tech users to tab through nav and headers linearly.
Fix
Render the primary content wrapper as a semantic `<main>` element instead of a generic `<div>`.
export function MainBody({ children }: { children: React.ReactNode }) {
return <div className={cn("grid grid-rows-1 overflow-hidden")}>{children}</div>;
}export function MainBody({ children }: { children: React.ReactNode }) {
return <main className={cn("grid grid-rows-1 overflow-hidden")}>{children}</main>;
}SettingsRowTitle only becomes an accessible label when callers remember htmlFor
`SettingsRowTitle` renders a `<label htmlFor={htmlFor}>` only if `htmlFor` is explicitly passed; otherwise it silently falls back to a bare `<span>`. `SettingsRow` forwards `htmlFor` but nothing in the API requires it when `action` is an interactive control (switch, select, input).
Why it matters
Every settings row that pairs a title like "Two-factor authentication" with a toggle in `action` needs that title programmatically associated with the control, or screen reader users hear an unlabeled switch. Because the association is opt-in, it's easy for a future row to ship with a visually-labeled but semantically unlabeled control.
Fix
Require or default the row's label association whenever an interactive action is present, instead of leaving htmlFor as an easy-to-skip optional prop.
const classes = cn("font-sans text-sm font-semibold leading-tight text-text-bright", className);
return htmlFor ? (const classes = cn("font-sans text-sm font-semibold leading-tight text-text-bright", className);
// Prefer always passing htmlFor from SettingsRow when `action` renders an input/switch/select,
// so the title is never silently unassociated with its control.
return htmlFor ? (Want this on your repo?
Rams reviews your next PR automatically and posts inline fix suggestions.
Review my public repo for freeColor
Hardcoded hex #101214 breaks theming for the table background layer
The third background div sets `backgroundColor: "#101214"` inline, while the wrapper around it uses the token class `bg-background-dimmed`. One layer is on the token system, the other is a raw hex value nobody can find or update from one place.
Why it matters
If the background token changes (theme tweak, new dark mode value), this layer silently drifts out of sync with the rest of the dashboard chrome, and there is no single source of truth to update.
Fix
Replace hardcoded hex values with the existing background design token.
backgroundSize: "1200px auto",
backgroundColor: "#101214",
}} backgroundSize: "1200px auto",
}}
className="... bg-background"Typography
Components
Motion
UX
Craft
Working well
- Divider color (`border-grid-dimmed`) and heading text color (`text-text-bright`) are used consistently across SettingsHeader, SettingsRow, and SettingsBlock instead of being redeclared with raw hex or ad hoc grays in each component. That's exactly what makes a token system pay off: one value to change, not three.
- SettingsSection's `[&:not(:first-child)]:mt-12` pattern gives consistent vertical rhythm between sections without double-margins on the first one. This is a smarter default than a plain `space-y-12` wrapper because it lets a single `SettingsSection` be dropped anywhere without knowing what comes before it.
- SettingsRow's `children` escape hatch (skipping the built-in title/description layout when children is provided) is a good API decision: it covers the composable common case while still giving callers a way out for custom left-hand content, instead of forcing a rigid title+description shape everywhere.
- PageBody correctly forwards a ref via forwardRef and toggles between `overflow-y-auto` and `overflow-hidden` based on a `scrollable` prop instead of hardcoding scroll behavior. This keeps the same primitive usable for both scrollable page bodies and fixed-height panels without a second component.
Score your own repo with Rams.
Free on public repos. Rams reviews every pull request for accessibility, color, type, spacing, components, motion, UX, and craft, and posts inline fixes.