Product Collection
Sticky filter sidebar, sort toolbar and product grid — with the facets dropped rather than stacked on mobile, so a shopping page does not open as a form.
The page between a listing and a product detail, with the two questions a comparison table alone cannot answer: will it fit, and can I actually have it.
app/product-compare-page.tsxnpx hoverlab add product-compare-page
Or over MCP, from your editor's agent — no account needed.
Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.
The collection
A grid of forty products with a one-line title is a search result. For anything considered, the reason the collection exists is what does the selling, and it belongs above the grid rather than on a linked story page nobody opens.
2 rows are identical across all 3 and hidden.
| Specification | Field Over-Ear£249 | Field Over-Ear Lite£179 | Studio Reference£329 |
|---|---|---|---|
| Price | £249 | £179best | £329 |
| Battery life | 38 hours | 44 hoursbest | 30 hours |
| Weight | 286 g | 241 gbest | 324 g |
| Noise cancelling | Adaptive, 3 levels | Passive only | Adaptive, 5 levels |
| Driver | 40 mm dynamic | 40 mm dynamic | 50 mm planar |
| Water resistance | IPX4 | IPX4 | None |
| Warranty | 2 years | 2 years | 5 yearsbest |
Specification
A forty-row spec table is a place to hide the three figures someone is actually comparing. These are pulled out; the full table is still below for the person who wants it.
Measurements are of the body, not the garment.
The model is 1.78m and wears a medium.
9 in 10 buyers kept their usual size. The 1 in 10 who exchanged went up, not down.
| Size | Chest (cm)Around the fullest part, under the arms, tape level and not pulled tight. | Waist (cm)Around the narrowest part, usually just above the navel. | Sleeve (cm)From the centre back of the neck, over the shoulder, to the wrist. |
|---|---|---|---|
| XS | 81–86 | 66–71 | 61 |
| S | 86–91 | 71–76 | 63 |
| Mworn by the model | 96–101 | 81–86 | 64 |
| L | 106–111 | 91–96 | 66 |
| XL | 116–121 | 101–106 | 67 |
| XXL | 126–131 | 111–116 | 69 |
Between two sizes on chest and waist? This shirt is cut straight, so take the larger — that is what the exchanges above did.
"In stock" on a listing that means "one of nine variants is in stock" is the single most expensive lie in commerce UI — it is discovered at checkout, and the basket is abandoned there.
A sold-out product page with a greyed-out button is a dead end and a lost address. One field turns it into the only useful thing left on the page.
The real page, rendered in your current theme — every section below is a live block, not a screenshot.
Only want one section? Open it and copy that block instead — browse Product Listings.
/**
* Deciding between three of a thing — the page between a listing and a
* product detail.
*
* Comparison pages get built as a table and stop there, which loses the
* two questions that actually decide the purchase: will it fit, and can I
* have it. Both are here.
*
* story an editorial opener with a real tablist for the thumbnail
* strip, so arrow keys work. A comparison that opens on a
* grid of specs assumes the reader already knows what they
* are looking at
* table three side by side, row labels pinned through the
* horizontal scroll, and an "only show differences" toggle
* that turns forty rows into the six that matter
* specs the detail behind the row that made someone pause
* sizing what the model is wearing and what previous buyers did —
* the panel that decides whether the parcel comes back
* stock per-variant, because "in stock" for a product is not a
* fact about the size you want
* back in and the form for when it is not
*
* The stock pair at the end is the part most comparison pages omit
* entirely, and it is the one that costs money in both directions: a
* reader who chooses the item that is unavailable buys nothing, and a
* reader told it is unavailable with no way to be notified is a lost sale
* that never appears in any funnel.
*/
import * as React from 'react'
import { CollectionStorySplit } from '@/components/collection-story-split'
import { ProductCompareTable } from '@/components/product-compare-table'
import { ProductSpecSplit } from '@/components/product-spec-split'
import { ProductSizeGuide } from '@/components/product-size-guide'
import { StockAvailabilityList } from '@/components/stock-availability-list'
import { BackInStockForm } from '@/components/back-in-stock-form'
export default function ProductComparePage() {
return (
<main className="min-h-screen bg-background text-foreground">
<CollectionStorySplit />
<ProductCompareTable />
<ProductSpecSplit />
<ProductSizeGuide />
{/* The two blocks a comparison page usually forgets. */}
<StockAvailabilityList />
<BackInStockForm />
</main>
)
}
components/ — each block page has its own copy button.app/product-compare-page.tsx. The imports already point at @/components/…, so they resolve with no edits.The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.
Sticky filter sidebar, sort toolbar and product grid — with the facets dropped rather than stacked on mobile, so a shopping page does not open as a form.
Gallery, sticky buy box, details accordion and reviews — ordered by what a shopper needs before they can decide.
Editable lines beside a sticky summary, so the total and the checkout button survive a scroll through four quantity changes.
Address and payment with the site navigation stripped and the order summary moved above the form on mobile — both aimed squarely at completion rate.
The post-purchase screen, with navigation restored and a rail that shows what to buy next rather than what was just bought.
Order history in the settings shell rather than a second sidebar layout — an account area and a settings area are the same shape.
Checkout for the buyer who will not fill in a form: wallet buttons above the fields, and the address a wallet returns shown and changeable before the charge rather than after it.
For the customer with an order number and no account — a lookup, an arrival range rather than a promised date, and the review form where the "how did we do" email actually lands.