vercel on GitHub

vercel/next-forge

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

30 files reviewed·July 25, 2026

View on GitHub

Elevated

Design risk in this codebase.

7issues
Critical & Serious · top 6 shown below

Top fix

Remove aria-hidden from the features section wrapper and headline

See the fix

Verdict

Motion and layout polish sit on top of an accessibility foundation that's actively broken, not just neglected. The biggest risk is a features section that communicates nothing to anyone, screen reader or sighted, because it's hidden and unreadable at once.

Files Rams reviewed

apps/web/app/[locale]/components/header/index.tsx

docs/app/[lang]/(home)/components/apps/index.tsx

docs/app/[lang]/(home)/components/features/index.tsx

apps/app/app/(authenticated)/components/avatar-stack.tsx

apps/app/app/(authenticated)/components/collaboration-provider.tsx

apps/app/app/(authenticated)/components/cursors.tsx

apps/app/app/(authenticated)/components/header.tsx

apps/app/app/(authenticated)/components/sidebar.tsx

apps/web/app/[locale]/(home)/components/cases.tsx

apps/web/app/[locale]/(home)/components/cta.tsx

apps/web/app/[locale]/(home)/components/faq.tsx

apps/web/app/[locale]/(home)/components/features.tsx

apps/web/app/[locale]/(home)/components/hero.tsx

apps/web/app/[locale]/(home)/components/stats.tsx

apps/web/app/[locale]/(home)/components/testimonials.tsx

apps/web/app/[locale]/components/footer.tsx

apps/web/app/[locale]/components/header/language-switcher.tsx

apps/web/app/[locale]/contact/components/contact-form.tsx

docs/app/[lang]/(home)/components/hero.tsx

docs/app/[lang]/(home)/components/installer.tsx

apps/app/app/(authenticated)/layout.tsx

apps/app/app/(authenticated)/page.tsx

apps/app/app/(authenticated)/search/page.tsx

apps/app/app/(unauthenticated)/layout.tsx

apps/app/app/layout.tsx

apps/web/app/[locale]/(home)/page.tsx

apps/web/app/[locale]/blog/[slug]/page.tsx

apps/web/app/[locale]/blog/page.tsx

apps/web/app/[locale]/layout.tsx

apps/web/app/[locale]/legal/[slug]/page.tsx

93/100

Accessibility

1 critical2 serious
AccessibilityCritical

docs/app/[lang]/(home)/components/features/index.tsx:177

Entire features section hidden from screen readers, headline included

The wrapper div holding the heading "Built with the best tools for modern developers" and the whole animated logo grid is marked aria-hidden="true" at the section root. There is no visually-hidden duplicate heading or aria-label anywhere in the component, so a screen reader user lands on section id="features" and gets nothing: no heading, no list of tools, no content at all.

Why it matters

A screen reader user skips this entire section as if it doesn't exist, losing the section's message and the tool list it's built to showcase.

Fix

Reserve aria-hidden for purely decorative layers and give the section a real accessible heading outside the hidden wrapper.

<section className="dark h-[400px] sm:h-[800px]" id="features">
  <div
    aria-hidden="true"
    className="relative h-full overflow-hidden bg-background py-24 ring-inset sm:py-32"
  >
<section className="dark h-[400px] sm:h-[800px]" id="features">
  <h2 className="sr-only">Built with the best tools for modern developers</h2>
  <div
    aria-hidden="true"
    className="relative h-full overflow-hidden bg-background py-24 ring-inset sm:py-32"
  >
AccessibilitySerious

apps/app/app/(authenticated)/components/avatar-stack.tsx:21

Presence avatar has no accessible name for keyboard or screen reader users

TooltipTrigger wraps an Avatar with no aria-label, and the name only appears inside TooltipContent ("<p>{info?.name ?? "Unknown"}</p>"), which is a hover/focus description, not a guaranteed accessible name on the trigger itself.

Why it matters

A screen reader user tabbing through the avatar stack hears an unlabeled control instead of the collaborator's name, so they can't tell who's present without hovering.

Fix

Set an explicit aria-label on the tooltip trigger so the accessible name is present independent of the tooltip's hover behavior.

<TooltipTrigger>
  <Avatar className="h-7 w-7 bg-secondary ring-1 ring-background">
<TooltipTrigger aria-label={info?.name ?? "Unknown user"}>
  <Avatar className="h-7 w-7 bg-secondary ring-1 ring-background">
AccessibilitySerious

apps/app/app/(authenticated)/components/avatar-stack.tsx:23

Avatar image alt text silently disappears when a name is missing

AvatarImage alt={info?.name} src={info?.avatar} falls back to alt={undefined} whenever info?.name is empty, leaving the rendered <img> without descriptive alt text.

Why it matters

When a collaborator's name hasn't loaded yet, the avatar image loses its only textual description, so a screen reader announces nothing for that presence indicator.

Fix

Provide a non-empty fallback string for alt so the image always has an accessible description.

<AvatarImage alt={info?.name} src={info?.avatar} />
<AvatarImage alt={info?.name ?? "Unknown user"} src={info?.avatar} />
97/100

Motion

1 critical
Design SystemCritical

docs/app/[lang]/(home)/components/features/index.tsx:200

Infinite marquee logos run forever with no reduced-motion guard

The logo pills use animation-iteration-count:infinite with animation-name:move-x and animation-play-state:running, all set via plain Tailwind arbitrary values with no prefers-reduced-motion media query anywhere in the file.

Why it matters

Users with vestibular disorders who have prefers-reduced-motion set still get a continuously scrolling wall of logos, which is the exact motion pattern that triggers dizziness and nausea in that population.

Fix

Wrap infinite animations in a prefers-reduced-motion: no-preference guard or pause them via a reduced-motion class variant.

"[--move-x-from:-100%] [--move-x-to:calc(100%+100cqw)] [animation-iteration-count:infinite] [animation-name:move-x] [animation-play-state:running] [animation-timing-function:linear]"
"[--move-x-from:-100%] [--move-x-to:calc(100%+100cqw)] motion-safe:[animation-iteration-count:infinite] motion-safe:[animation-name:move-x] motion-safe:[animation-play-state:running] [animation-timing-function:linear] motion-reduce:[animation-play-state:paused]"

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 free
98/100

Color

1 serious
ColorSerious

docs/app/[lang]/(home)/components/features/index.tsx:183

Headline text rendered at ~10% opacity is unreadable to sighted users

The paragraph "Built with the best tools for modern developers" is styled text-foreground/10, dropping it to roughly 10% opacity against bg-background inside a dark section (className="dark").

Why it matters

At 10% opacity the text sits far below the 4.5:1 contrast floor for body text, so sighted users can't read the section's own headline, only a faint smear behind the moving logos.

Fix

Raise the headline to a legible opacity or weight so it reads as a genuine heading, not a barely-visible watermark.

<p className="mx-auto mt-2 max-w-3xl text-pretty font-semibold text-4xl text-foreground/10 tracking-tight sm:text-5xl md:text-6xl">
<p className="mx-auto mt-2 max-w-3xl text-pretty font-semibold text-4xl text-foreground/40 tracking-tight sm:text-5xl md:text-6xl">
98/100

Craft

1 serious
Design SystemSerious

docs/app/[lang]/(home)/components/features/index.tsx:176

Arbitrary pixel and percentage values scattered through the hero wrapper

The section mixes h-[400px], max-w-[90%], bg-[length:12px_100%], and from-[2px] arbitrary values instead of the Tailwind spacing/sizing scale, all within a few lines of markup.

Why it matters

Every arbitrary value here is a one-off that the design system's scale doesn't track, so future edits to spacing or breakpoints have to be found and fixed individually instead of through a shared token.

Fix

Replace one-off arbitrary values with the closest scale step (h-96, max-w-[theme value] or a defined token) so the section stays adjustable from one place.

<section className="dark h-[400px] sm:h-[800px]" id="features">
  ...
  <div className="absolute top-1/2 left-1/2 mx-auto w-full max-w-[90%] -translate-x-1/2 -translate-y-1/2 text-center">
<section className="dark h-96 sm:h-[50rem]" id="features">
  ...
  <div className="absolute top-1/2 left-1/2 mx-auto w-full max-w-4xl -translate-x-1/2 -translate-y-1/2 text-center">

Typography

No issues found

Spacing

No issues found

Components

No issues found

UX

No issues found

Working well

  • Staggering each logo row's animation-delay and animation-duration (e.g. "[animation-delay:-33s] [animation-duration:40s]") gives the marquee organic variation instead of every row scrolling in identical lockstep, a small touch that avoids the wall-of-motion feel.
  • The avatar stack's -space-x-1 overlap with ring-1 ring-background is a clean, standard way to show stacked presence: the ring keeps overlapping avatars visually separated without adding extra borders or colors.

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 vercel/next-forge’s UI code: 30 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