vercel/next.js
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 29, 2026
More findings
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
Accessibility
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"
/>Typography
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 freeSpacing
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"
/>Components
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;
}) {UX
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
Motion
Craft
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.