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

vercel/commerce

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

30 files reviewed·July 9, 2026

Top fix

Add a homepage title in metadata for tabs and search

See the fix

Verdict

Solid componentry undone by skipped details: clean layouts and token-based colors sit next to missing landmarks, no page title, and broken list semantics. None of these ten issues are critical alone, but together they signal a codebase that never got a final accessibility pass.

Files Rams reviewed

app/[page]/page.tsx

app/layout.tsx

app/product/[handle]/page.tsx

app/search/[collection]/page.tsx

app/search/layout.tsx

app/search/page.tsx

app/[page]/layout.tsx

app/page.tsx

components/layout/navbar/index.tsx

components/layout/search/filter/index.tsx

components/carousel.tsx

components/cart/add-to-cart.tsx

components/cart/cart-context.tsx

components/cart/delete-item-button.tsx

components/cart/edit-item-quantity-button.tsx

components/cart/modal.tsx

components/grid/three-items.tsx

components/grid/tile.tsx

components/label.tsx

components/layout/footer-menu.tsx

components/layout/footer.tsx

components/layout/navbar/mobile-menu.tsx

components/layout/navbar/search.tsx

components/layout/product-grid-items.tsx

components/layout/search/collections.tsx

components/layout/search/filter/dropdown.tsx

components/layout/search/filter/item.tsx

components/opengraph-image.tsx

components/product/gallery.tsx

components/product/product-description.tsx

92/100

Accessibility

4 serious
Accessibility·components/layout/navbar/index.tsx:34Serious

Desktop menu links have no vertical padding, shrinking the tap target

The primary nav items ('Home', etc., rendered from `menu.map`) sit inside `<li>` with the `<Link>` carrying only `text-neutral-500 underline-offset-4 hover:text-black hover:underline` and no padding. The clickable area equals the text-sm line box, roughly 20px tall, on a list that renders as soon as the viewport hits `md`.

Why it matters

Below the 24px WCAG 2.5.8 AA floor, users on trackpads or touch-capable tablets in the md range mis-click between adjacent nav items, and every misclick costs a repeat attempt.

Fix

Add vertical padding to the link itself so the hit area grows without changing the visible text size or gap rhythm.

<Link
  href={item.path}
  prefetch={true}
  className="text-neutral-500 underline-offset-4 hover:text-black hover:underline dark:text-neutral-400 dark:hover:text-neutral-300"
>
  {item.title}
</Link>
<Link
  href={item.path}
  prefetch={true}
  className="inline-block py-2 text-neutral-500 underline-offset-4 hover:text-black hover:underline dark:text-neutral-400 dark:hover:text-neutral-300"
>
  {item.title}
</Link>
Accessibility·components/layout/search/filter/index.tsx:27Serious

Unlabeled nav landmark becomes ambiguous when filters repeat on a page

The `<nav>` wrapping the filter list has no `aria-label`. This component is generic (`title` prop varies), meaning a search page likely renders it multiple times for different facets (category, price, sort).

Why it matters

Screen reader users navigating by landmark hear multiple unnamed 'navigation' regions with no way to tell which is the price filter versus the category filter, so finding the right control by landmark navigation breaks down.

Fix

Give each nav landmark an accessible name derived from its title.

<nav>
  {title ? (
<nav aria-label={title || "Filter"}>
  {title ? (
Accessibility·app/layout.tsx:30Serious

Dark mode ships with no color-scheme, so browser chrome stays light

The `<body>` carries `dark:bg-neutral-900 dark:text-white` but neither `<html>` nor `<body>` sets `color-scheme`. Native form controls, scrollbars, and the browser UI (address bar theme, autofill dropdowns) stay light-themed while the page itself renders dark.

Why it matters

Users in dark mode see a jarring light scrollbar and light native inputs/selects against a dark page, which reads as an unfinished theme rather than a designed one.

Fix

Set color-scheme to light dark on the root element so native UI follows the same theme as the page.

<html lang="en" className={GeistSans.variable}>
<html lang="en" className={GeistSans.variable} style={{ colorScheme: "light dark" }}>
Accessibility·components/layout/search/filter/index.tsx:40Serious

Dropdown rendered as direct child of a `<ul>` breaks list semantics

`<ul className="md:hidden"><FilterItemDropdown list={list} /></ul>` places a dropdown component directly inside a `<ul>` with no `<li>` wrapper, while the sibling desktop `<ul>` correctly wraps each `FilterItem` in list markup via `.map`.

Why it matters

A `<ul>` whose only valid children are `<li>` elements gets its list semantics dropped by assistive tech when a non-li child appears, so screen reader users lose the 'list of N items' announcement on mobile even though desktop keeps it.

Fix

Only place `<li>` elements as direct children of `<ul>`, or swap the wrapper to a plain `<div>` when the child isn't list markup.

<ul className="md:hidden">
  <Suspense fallback={null}>
    <FilterItemDropdown list={list} />
  </Suspense>
</ul>
<div className="md:hidden">
  <Suspense fallback={null}>
    <FilterItemDropdown list={list} />
  </Suspense>
</div>
96/100

UX

2 serious
UX·app/page.tsx:5Serious

Homepage metadata has no title, so browser tabs and search results show a fallback

The `metadata` export only sets `description` and `openGraph.type`. There is no `title` field, so this page inherits whatever generic title the root layout defines (or none at all) instead of naming the homepage.

Why it matters

The tab title and search result headline are often the first thing a user or a search engine sees. A missing title makes the homepage indistinguishable from any other page in a browser's tab bar, history, or bookmarks.

Fix

Give every page-level metadata export an explicit, descriptive title.

export const metadata = {
  description:
    "High-performance ecommerce store built with Next.js, Vercel, and Shopify.",
  openGraph: {
    type: "website",
  },
};
export const metadata = {
  title: "Home",
  description:
    "High-performance ecommerce store built with Next.js, Vercel, and Shopify.",
  openGraph: {
    type: "website",
  },
};
UX·components/layout/navbar/index.tsx:27Serious

Site name vanishes at md then reappears at lg, an inconsistent brand mark

The `SITE_NAME` div next to `LogoSquare` uses `md:hidden lg:block`, so the wordmark is visible on mobile, disappears at the `md` breakpoint, then comes back at `lg`. Every other breakpoint transition in this file moves in one direction (hidden mobile menu appears at md, search appears at md); this one flickers on-off-on.

Why it matters

A brand mark that disappears and reappears across breakpoints reads as an unfinished responsive pass rather than an intentional state, and it breaks the otherwise consistent progressive-disclosure pattern the rest of the header follows.

Fix

Pick one direction: reveal the wordmark from a single breakpoint upward instead of toggling it off and back on.

<div className="ml-2 flex-none text-sm font-medium uppercase md:hidden lg:block">
  {SITE_NAME}
</div>
<div className="ml-2 hidden flex-none text-sm font-medium uppercase tracking-wide lg:block">
  {SITE_NAME}
</div>

Typography

No issues found

Color

No issues found

Spacing

No issues found

Components

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • The page component does one job: it composes `ThreeItemGrid`, `Carousel`, and `Footer` with no inline markup or styling of its own. That restraint matters because it keeps section-level layout decisions (grid rules, carousel behavior) owned by their own components instead of leaking into the page, which makes each piece independently testable and reusable on other routes.
  • Declaring `openGraph: { type: "website" }` on the homepage metadata is the correct Open Graph type for a storefront root, rather than defaulting to `article` or leaving it unset. Getting this right means link previews on social platforms render the homepage correctly instead of defaulting to a generic card.
  • Splitting the filter into a full `<ul>` list for desktop (`hidden md:block`) and a `FilterItemDropdown` for mobile (`md:hidden`) is the right call: a horizontal list of filter links doesn't fit small viewports, and collapsing it into a dropdown preserves the same data without cramming touch targets.
  • The nav links use semantic Tailwind color tokens (`text-neutral-500` / `dark:text-neutral-400`) with matched hover and dark variants instead of hardcoded hex, which means the whole nav repaints correctly under dark mode with zero extra work.

Score your own repo with Rams.

Free on public repos. Rams reviews every pull request for accessibility, color, type, spacing, components, motion, UX, and craft, and posts inline fixes.