steven-tey on GitHub

steven-tey/precedent

Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.

25 files reviewed·July 25, 2026

View on GitHub

Elevated

Design risk in this codebase.

4issues
Critical & Serious

Top fix

Make tooltip trigger a focusable, keyboard-operable element

See the fix

Verdict

Surface polish outruns interaction discipline: nice type and CTA hierarchy sit next to focus rings stripped with nothing to replace them. The biggest risk is a tooltip trigger that keyboard users can't reach at all.

Files Rams reviewed

app/layout.tsx

app/page.tsx

app/opengraph-image.tsx

components/home/card.tsx

components/home/component-grid.tsx

components/home/demo-modal.tsx

components/home/web-vitals.tsx

components/layout/navbar.tsx

components/shared/counting-numbers.tsx

components/shared/icons/buymeacoffee.tsx

components/shared/icons/expanding-arrow.tsx

components/shared/icons/github.tsx

components/shared/icons/google.tsx

components/shared/icons/loading-circle.tsx

components/shared/icons/loading-spinner.module.css

components/shared/modal.tsx

components/shared/popover.tsx

components/shared/tooltip.tsx

app/globals.css

components/shared/icons/index.tsx

components/layout/footer.tsx

components/shared/icons/loading-dots.module.css

components/shared/icons/loading-dots.tsx

components/shared/icons/loading-spinner.tsx

components/shared/icons/twitter.tsx

95/100

Accessibility

1 critical1 serious
AccessibilityCritical

components/home/component-grid.tsx:50

Tooltip trigger div can never receive keyboard focus

The "Tooltip" trigger in components/home/component-grid.tsx is a plain `<div>` with `cursor-default` and no `tabIndex`, `role`, or keyboard handler, wrapped by the `Tooltip` component. A mouse user can hover it to see the tooltip content, but there is no way to Tab to it.

Why it matters

Keyboard-only and screen-reader users can never trigger this element at all, so the tooltip content is permanently unreachable for them, which is a full lockout rather than a degraded experience.

Fix

Make any element that reveals content on hover also focusable and revealed on focus, using a native button or an explicit tabIndex with keyboard handling.

<Tooltip content="Precedent is an opinionated collection of components, hooks, and utilities for your Next.js project.">
  <div className="flex w-36 cursor-default items-center justify-center rounded-md border border-gray-300 px-3 py-2 transition-all duration-75 hover:border-gray-800 focus:outline-none active:bg-gray-100">
    <p className="text-gray-600">Tooltip</p>
  </div>
</Tooltip>
<Tooltip content="Precedent is an opinionated collection of components, hooks, and utilities for your Next.js project.">
  <button
    type="button"
    className="flex w-36 cursor-default items-center justify-center rounded-md border border-gray-300 px-3 py-2 transition-all duration-75 hover:border-gray-800 focus-visible:ring-2 focus-visible:ring-gray-400 active:bg-gray-100"
  >
    <p className="text-gray-600">Tooltip</p>
  </button>
</Tooltip>
AccessibilitySerious

components/home/component-grid.tsx:17

Modal and Popover buttons strip focus rings with no replacement

Both the "Modal" trigger button and the "Popover" trigger button in components/home/component-grid.tsx apply `focus:outline-none` with no `focus-visible` ring or alternate focus style to replace it. Only `hover:` and `active:` states are styled.

Why it matters

Keyboard users tabbing through the grid get no visual indication of which control currently has focus, so they lose track of position on the page and can't confirm what pressing Enter will activate.

Fix

Never remove the default focus outline without adding a visible focus-visible style as a replacement.

<button
  onClick={() => setShowDemoModal(true)}
  className="flex w-36 items-center justify-center rounded-md border border-gray-300 px-3 py-2 transition-all duration-75 hover:border-gray-800 focus:outline-none active:bg-gray-100"
>
  <p className="text-gray-600">Modal</p>
</button>
<button
  onClick={() => setShowDemoModal(true)}
  className="flex w-36 items-center justify-center rounded-md border border-gray-300 px-3 py-2 transition-all duration-75 hover:border-gray-800 focus-visible:ring-2 focus-visible:ring-gray-400 active:bg-gray-100"
>
  <p className="text-gray-600">Modal</p>
</button>
96/100

Craft

2 serious
CraftSerious

components/home/card.tsx:23

Card heading title text is transparent, relying on a gradient clip

In components/home/card.tsx, the h2 rendering `{title}` uses `bg-gradient-to-br from-black to-stone-500 bg-clip-text text-transparent` instead of a solid text color. Every card title on the homepage grid inherits this same decorative fill, so the effect repeats across the whole section rather than being an intentional one-off accent.

Why it matters

Gradient-clipped text depends on the browser correctly compositing background-clip across every viewport and zoom level; any fallback failure (older browsers, high-contrast mode, print) drops the title to invisible transparent text, and it also caps the heading's contrast below what a solid dark color would give against the white card background.

Fix

Use a solid ink color for heading text and reserve gradients for non-text decorative surfaces.

<h2 className="bg-gradient-to-br from-black to-stone-500 bg-clip-text font-display text-xl font-bold text-transparent [text-wrap:balance] md:text-3xl md:font-normal">
  {title}
</h2>
<h2 className="font-display text-xl font-bold text-gray-900 [text-wrap:balance] md:text-3xl md:font-normal">
  {title}
</h2>
CraftSerious

app/opengraph-image.tsx:33

OG image headline uses gradient-clipped text instead of solid ink

The h1 "Precedent" in app/opengraph-image.tsx fills its glyphs with a black-to-stone-500 gradient via backgroundClip: 'text' and color: 'transparent' instead of a solid color. This is the generated-image equivalent of gradient-text templates, and it renders inconsistently across social platforms that strip or flatten CSS backgrounds in OG image scrapers.

Why it matters

OG images are rendered once and cached by link-unfurling crawlers (Twitter, Slack, iMessage); if the gradient clip fails to rasterize correctly in the satori/resvg pipeline, the brand name can render faint or invisible in shared links, which is the first impression for anyone clicking a shared URL.

Fix

Use a solid, high-contrast fill color for headline text in generated share images so it renders reliably across all unfurl renderers.

<h1
  style={{
    fontSize: "100px",
    fontFamily: "SF Pro",
    background:
      "linear-gradient(to bottom right, #000000 21.66%, #78716c 86.47%)",
    backgroundClip: "text",
    color: "transparent",
    lineHeight: "5rem",
    letterSpacing: "-0.02em",
  }}
>
  Precedent
</h1>
<h1
  style={{
    fontSize: "100px",
    fontFamily: "SF Pro",
    color: "#000000",
    lineHeight: "5rem",
    letterSpacing: "-0.02em",
  }}
>
  Precedent
</h1>

Typography

No issues found

Color

No issues found

Spacing

No issues found

Components

No issues found

Motion

No issues found

UX

No issues found

Working well

  • The two footer CTAs are clearly differentiated: 'Deploy to Vercel' is a solid black-filled button while 'Star on GitHub' is an outlined white button with a shadow, so the primary action reads instantly without relying on color alone.
  • Setting lang="en" on the <html> element is correct and easy to miss: it lets screen readers and browsers pick the right pronunciation and hyphenation rules from the very first paint.
  • The popover menu items ('Item 1', 'Item 2', 'Item 3') use consistent hover/active states across all three buttons, giving predictable feedback for that one interaction pattern.

Scored July 25, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored July 9, 2026: 59/100. This rescore on v0.0.3: 59/100.

This page is an automated design review of steven-tey/precedent’s UI code: 25 files read against 309 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.

Or get a design review on every pull requestInstall Rams