Rams MCP · The full engine, now in your coding agent
vercel on GitHub

vercel/next.js

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

30 files reviewed·July 29, 2026

View on GitHub

Top fix

Add keyboard support to the sidebar resize control

See the fix

Verdict

A resize handle with correct clamping math but no robust interaction model: too thin to grab, mouse-only, and prone to getting stuck mid-drag. The bigger risk is that keyboard users are locked out entirely, while stray any types elsewhere show the same pattern of skipping the safety net.

Files Rams reviewed

examples/blog-starter/src/app/layout.tsx

examples/blog-starter/src/app/posts/[slug]/page.tsx

examples/cms-wordpress/src/app/[[...slug]]/page.tsx

examples/with-redux/app/components/counter/Counter.module.css

examples/with-redux/app/components/counter/Counter.tsx

examples/with-redux/app/components/quotes/Quotes.tsx

examples/with-stripe-typescript/app/components/CheckoutForm.tsx

examples/with-stripe-typescript/app/components/ElementsForm.tsx

packages/next/src/client/components/client-page.tsx

packages/next/src/next-devtools/dev-overlay/components/errors/error-overlay-layout/error-overlay-layout.tsx

apps/bundle-analyzer/app/layout.tsx

apps/bundle-analyzer/app/page.tsx

evals/evals/agent-043-view-transitions/app/product/[slug]/page.tsx

examples/blog-starter/src/app/page.tsx

examples/cache-handler-redis/app/[timezone]/page.tsx

examples/cms-contentful/app/layout.tsx

examples/cms-contentful/app/page.tsx

examples/cms-contentful/app/posts/[slug]/page.tsx

examples/cms-payload/app/(site)/[slug]/page.tsx

examples/cms-sanity/app/(blog)/layout.tsx

examples/cms-sanity/app/(blog)/page.tsx

examples/cms-sanity/app/(blog)/posts/[slug]/page.tsx

examples/cms-wordpress/src/app/layout.tsx

examples/image-component/app/color/page.tsx

examples/image-component/app/fill/page.tsx

examples/image-component/app/page.tsx

examples/image-component/app/shimmer/page.tsx

examples/image-component/app/theme/page.tsx

examples/inngest/src/app/layout.tsx

examples/inngest/src/app/page.tsx

98/100

Accessibility

1 serious
AccessibilitySerious

apps/bundle-analyzer/app/page.tsx:233

Sidebar resize button responds only to mouse, blocking keyboard users

The resize handle button (aria-label="Resize sidebar") wires onMouseDown to handleMouseDown but has no onKeyDown or arrow-key handler. A button that is labelled as a control and receives focus but does nothing on Enter, Space, or arrow keys leaves keyboard users unable to perform the one action it exists for.

Why it matters

Keyboard-only and switch-device users can tab to "Resize sidebar" but have no way to actually resize the panel, so the control is present in the accessibility tree but functionally dead for them.

Fix

Add a keydown handler that adjusts sidebarWidth on ArrowLeft/ArrowRight so the control is operable without a mouse.

<button
  type="button"
  className="flex-none w-1 bg-border hover:bg-primary cursor-col-resize transition-colors"
  onMouseDown={handleMouseDown}
  aria-label="Resize sidebar"
/>
<button
  type="button"
  className="flex-none w-1 bg-border hover:bg-primary cursor-col-resize transition-colors"
  onMouseDown={handleMouseDown}
  onKeyDown={(e) => {
    if (e.key === 'ArrowLeft') setSidebarWidth((w) => Math.max(10, w - 2))
    if (e.key === 'ArrowRight') setSidebarWidth((w) => Math.min(50, w + 2))
  }}
  aria-label="Resize sidebar"
/>
98/100

Typography

1 serious
TypographySerious

apps/bundle-analyzer/app/page.tsx:255

Hovered file name can overflow the fixed-height status bar

{hoveredNodeInfo.name} renders inside a <span className="font-medium text-foreground"> with no truncate, max-width, or title attribute, inside a status bar fixed at h-10. Long module paths (common in bundle output) have nothing stopping them from wrapping or overflowing.

Why it matters

A long file path wraps the h-10 bar to a second line or spills past its container, breaking the status bar's fixed height and pushing or clipping the size/badge info next to it.

Fix

Truncate long names with an ellipsis and expose the full path via a title attribute for hover.

<span className="font-medium text-foreground">
  {hoveredNodeInfo.name}
</span>
<span className="font-medium text-foreground truncate max-w-xs inline-block align-bottom" title={hoveredNodeInfo.name}>
  {hoveredNodeInfo.name}
</span>

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

Spacing

1 serious
SpacingSerious

apps/bundle-analyzer/app/page.tsx:232

4px-wide resize handle gives almost no room to grab it

The drag handle button uses w-1 (4px) with no extended hit area, padding, or invisible hover zone beyond its visual width. Both the loading-state version (line 196) and active version (line 232) ship at this width.

Why it matters

A 4px target is far below comfortable click/tap precision, so users miss the handle on the first attempt and have to hunt for the exact pixel column before they can resize the panel.

Fix

Keep the visual bar thin but extend the clickable/touchable hit area with padding or a pseudo-element so the target is at least ~12-16px wide.

<button
  type="button"
  className="flex-none w-1 bg-border hover:bg-primary cursor-col-resize transition-colors"
  onMouseDown={handleMouseDown}
  aria-label="Resize sidebar"
/>
<button
  type="button"
  className="relative flex-none w-1 bg-border hover:bg-primary cursor-col-resize transition-colors before:content-[''] before:absolute before:inset-y-0 before:-left-2 before:-right-2"
  onMouseDown={handleMouseDown}
  aria-label="Resize sidebar"
/>
98/100

Components

1 serious
ComponentsSerious

examples/cms-contentful/app/page.tsx:48

HeroPost's coverImage and author props typed as any lose compile-time safety

HeroPost destructures coverImage and author and types both as any. coverImage.url is accessed directly on line 57 and author is passed further into rendering, but nothing in the type system verifies either shape matches what CoverImage or the author display actually expects.

Why it matters

A schema change in the Contentful content model (renamed field, missing author object) fails silently at the type level and only shows up as a broken image or crash at runtime, increasing debugging time when the CMS content changes.

Fix

Replace any with concrete interfaces (or the existing Post/Author types) so prop shape mismatches are caught at compile time.

}: {
  title: string;
  coverImage: any;
  date: string;
  excerpt: string;
  author: any;
  slug: string;
}) {
}: {
  title: string;
  coverImage: { url: string };
  date: string;
  excerpt: string;
  author: { name: string; picture: { url: string } };
  slug: string;
}) {
98/100

UX

1 serious
UXSerious

apps/bundle-analyzer/app/page.tsx:167

Dragging the resize handle can get stuck if the cursor leaves the main element

handleMouseUp only fires through onMouseUp on the <main> element. If the user drags fast enough that the pointer exits the main container (or the mouse button is released over another element/iframe) before the up event fires, isResizing never resets to false, leaving the sidebar tracking mouse movement indefinitely.

Why it matters

A resize drag that doesn't reliably end traps the layout in a resizing state, forcing the user to click again just to stop the sidebar from following their cursor.

Fix

Bind mouseup to the window while isResizing is true instead of relying on the bounding element to catch the release.

<main
  className="h-screen flex flex-col bg-background"
  onMouseMove={handleMouseMove}
  onMouseUp={handleMouseUp}
>
useEffect(() => {
  if (!isResizing) return
  window.addEventListener('mouseup', handleMouseUp)
  return () => window.removeEventListener('mouseup', handleMouseUp)
}, [isResizing])

<main
  className="h-screen flex flex-col bg-background"
  onMouseMove={handleMouseMove}
>

Color

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • The 'Next.js' and CMS name links in the intro copy share one consistent hover:text-success duration-200 transition-colors treatment, keeping link affordance predictable across the same sentence.
  • The resize handler clamps sidebarWidth with Math.max(10, Math.min(50, newWidth)), so users can't drag the sidebar into a collapsed or oversized layout no matter how far they pull the handle.
  • MoreStories only renders when morePosts.length > 0, avoiding an empty, confusing section heading when there's just a single post to show.

Scored July 29, 2026 with Rams Engine v0.0.3 · Engine changelog

This page is an automated design review of vercel/next.js’s UI code: 30 files read against 258 versioned rules covering accessibility, color, typography, spacing, components, UX, motion, and craft. The score is out of 100; any confirmed critical issue caps it at 59.

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