vercel/commerce
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 25, 2026
More findings
Verdict
Structural instincts are strong, but spacing discipline is an afterthought: real breakpoints get skipped in favor of one-off pixel values. The biggest risk is inconsistent loading placeholders, where some elements reserve space and others just pop in.
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
Spacing
app/product/[handle]/page.tsx:125
Related product tiles use an ad hoc 475px breakpoint outside the scale
The related product `<li>` at line 125 uses `min-[475px]:w-1/2`, and the matching `sizes` attribute on `GridTileImage` (line 141) repeats the same `475px` value inline. Neither Tailwind's default screens (`sm: 640px`, `md: 768px`, etc.) nor a configured custom screen defines this breakpoint, so it's a one-off number duplicated in two places.
Why it matters
A breakpoint invented once and repeated as a string in two attributes will drift out of sync the first time someone edits one but not the other, breaking the tile grid's responsive alignment with its own `sizes` hint and causing the browser to fetch the wrong image size.
Fix
Define the custom breakpoint once in the Tailwind config as a named screen so both the class and the sizes attribute reference the same source.
className="aspect-square w-full flex-none min-[475px]:w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5"className="aspect-square w-full flex-none xs:w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5" // add `xs: '475px'` to theme.screens in tailwind.configapp/product/[handle]/page.tsx:88
Gallery loading skeleton locked to an arbitrary 550px height
The Suspense fallback for the product gallery, `<div className="relative aspect-square h-full max-h-[550px] w-full overflow-hidden" />` (line 88), pins the loading placeholder to a one-off pixel value that exists nowhere else in the scale. `aspect-square` already derives height from width, so the arbitrary cap is redundant and untracked.
Why it matters
A bespoke pixel value like this drifts the moment someone resizes the real Gallery component or changes the container padding, and the skeleton silently stops matching the loaded content, causing a layout jump on hydration.
Fix
Let aspect-square control the fallback's height instead of pinning an arbitrary pixel cap.
<div className="relative aspect-square h-full max-h-[550px] w-full overflow-hidden" /><div className="relative aspect-square w-full overflow-hidden" />UX
app/product/[handle]/page.tsx:101
Product title and price column pops in with no loading placeholder
The `ProductDescription` Suspense boundary at line 101 uses `fallback={null}`, so while the title, price, and options stream in, the right-hand column (`basis-full lg:basis-2/6`) renders completely empty. The gallery on the left has a sized skeleton, but this column has nothing holding its space.
Why it matters
The description column has no reserved height while streaming, so when it resolves the layout shifts and the page reflows around it, which reads as janky next to the gallery's already-sized fallback.
Fix
Give the ProductDescription boundary a skeleton fallback that reserves the same width and approximate height as the loaded content.
<Suspense fallback={null}>
<ProductDescription product={product} />
</Suspense><Suspense fallback={<div className="h-[400px] w-full animate-pulse rounded-lg bg-neutral-100 dark:bg-neutral-900" />}>
<ProductDescription product={product} />
</Suspense>Accessibility
Typography
Color
Components
Motion
Craft
Working well
- GridTileImage in the related products list receives a real `alt={product.title}` and the entire tile is wrapped in a single `Link`, giving users one clear, keyboard-reachable hit target instead of nested clickable regions.
- The gallery Suspense boundary ships a sized skeleton (`aspect-square`, `max-h-[550px]`) rather than `null`, which is the right instinct for avoiding layout shift even though the exact value needs cleanup.
- `RelatedProducts` returns `null` early when there are no recommendations (`if (!relatedProducts.length) return null;`), avoiding an empty heading and empty list from rendering for no reason.
Scored July 25, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored July 9, 2026: 80/100. This rescore on v0.0.3: 94/100.
This page is an automated design review of vercel/commerce’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.