vercel/next-forge
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 25, 2026
Elevated
Design risk in this codebase.
More findings
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
Accessibility
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"
>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">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} />Motion
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 freeColor
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">Craft
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
Spacing
Components
UX
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.