Order Confirmation
The thank-you screen built to prevent two support emails: a selectable order number and a delivery date range rather than "soon".
Sortable reviews with verified-purchase badges, optimistic helpful votes and a real expand control instead of a fade that hides text.
components/review-list.tsxnpx hoverlab add review-list
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.
Start a page with this section — add more, order them, and leave with the page source.
I bought this expecting to return it and it has become the thing I reach for three days a week. The knit is dense without being heavy, and after four washes there is no pilling at the cuffs where I would expect it first. Sizing is honest — I am usually a medium and the medium fits with room for a shirt underneath.
The stone reads much warmer in person than on screen — closer to a light camel. Lovely sweater, just not the colour I was picturing.
No complaints about the material at all. The body is about an inch longer than I expected, which is fine untucked but worth knowing if you are between sizes.
Two in stone, one in navy. They hold their shape better than anything else I own at twice the price.
Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.
'use client'
/**
* <ReviewList> — customer reviews with sorting and helpful votes.
*
* The "verified purchase" badge is the most valuable element here and the
* one most often left out. A review page where nothing distinguishes a
* buyer from a passer-by is worth less than no reviews at all, because
* readers discount all of it.
*
* The helpful vote is optimistic — the count moves on click and the request
* catches up. For an action this small, waiting for a round trip before the
* number changes reads as a broken button, and the cost of being wrong is
* one wrong count until the next load.
*
* Long reviews are clamped with a real expand control rather than being cut
* off with a fade. A gradient that hides text with no way to reveal it is a
* dark pattern by accident.
*/
import * as React from 'react'
import { Star, BadgeCheck, ThumbsUp } from 'lucide-react'
export interface Review {
id: string
author: string
rating: number
title: string
body: string
/** ISO date. */
date: string
verified?: boolean
helpfulCount: number
/** Size/fit note, e.g. "Bought M · usually M". */
context?: string
}
export interface ReviewListProps {
reviews?: Review[]
locale?: string
className?: string
}
const DEFAULT_REVIEWS: Review[] = [
{
id: '1',
author: 'Hannah T.',
rating: 5,
title: 'Better than the price suggests',
body: 'I bought this expecting to return it and it has become the thing I reach for three days a week. The knit is dense without being heavy, and after four washes there is no pilling at the cuffs where I would expect it first. Sizing is honest — I am usually a medium and the medium fits with room for a shirt underneath.',
date: '2026-07-28',
verified: true,
helpfulCount: 34,
context: 'Bought M · usually M',
},
{
id: '2',
author: 'Devin R.',
rating: 4,
title: 'Great, but runs slightly long',
body: 'No complaints about the material at all. The body is about an inch longer than I expected, which is fine untucked but worth knowing if you are between sizes.',
date: '2026-07-14',
verified: true,
helpfulCount: 12,
context: 'Bought L · usually L',
},
{
id: '3',
author: 'Priya M.',
rating: 5,
title: 'Third one I have bought',
body: 'Two in stone, one in navy. They hold their shape better than anything else I own at twice the price.',
date: '2026-06-30',
verified: true,
helpfulCount: 8,
},
{
id: '4',
author: 'Sam K.',
rating: 3,
title: 'Nice but the colour is off',
body: 'The stone reads much warmer in person than on screen — closer to a light camel. Lovely sweater, just not the colour I was picturing.',
date: '2026-06-11',
helpfulCount: 21,
},
]
type SortKey = 'recent' | 'helpful' | 'highest' | 'lowest'
function formatDate(iso: string, locale = 'en-GB'): string {
return new Date(`${iso}T00:00:00Z`).toLocaleDateString(locale, {
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: 'UTC',
})
}
export function ReviewList({
reviews = DEFAULT_REVIEWS,
locale = 'en-GB',
className = '',
}: ReviewListProps) {
// Per-instance ids. A literal id in a reusable component is a
// collision waiting for the second copy on the page — and a <label>
// then resolves to whichever input rendered first.
const uid = React.useId()
const [sort, setSort] = React.useState<SortKey>('helpful')
const [voted, setVoted] = React.useState<Set<string>>(() => new Set())
const [expanded, setExpanded] = React.useState<Set<string>>(() => new Set())
const sorted = React.useMemo(() => {
const copy = [...reviews]
copy.sort((a, b) => {
if (sort === 'recent') return b.date.localeCompare(a.date)
if (sort === 'helpful') return b.helpfulCount - a.helpfulCount
if (sort === 'highest') return b.rating - a.rating
return a.rating - b.rating
})
return copy
}, [reviews, sort])
function toggleVote(id: string) {
setVoted((prev) => {
const next = new Set(prev)
if (next.has(id)) next.delete(id)
else next.add(id)
return next
})
}
function toggleExpanded(id: string) {
setExpanded((prev) => {
const next = new Set(prev)
if (next.has(id)) next.delete(id)
else next.add(id)
return next
})
}
return (
<section id={`${uid}-reviews`} className={`w-full ${className}`}>
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-border/60 pb-4">
<h2 className="text-lg font-bold tracking-tight">
{reviews.length} {reviews.length === 1 ? 'review' : 'reviews'}
</h2>
<div className="flex items-center gap-2">
<label htmlFor={`${uid}-review-sort`} className="text-sm text-muted-foreground">
Sort
</label>
<select
id={`${uid}-review-sort`}
value={sort}
onChange={(e) => setSort(e.target.value as SortKey)}
className="rounded-xl border border-border/60 bg-background px-2.5 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-primary"
>
<option value="helpful">Most helpful</option>
<option value="recent">Most recent</option>
<option value="highest">Highest rated</option>
<option value="lowest">Lowest rated</option>
</select>
</div>
</div>
<ul className="divide-y divide-border/40">
{sorted.map((review) => {
const isVoted = voted.has(review.id)
const isExpanded = expanded.has(review.id)
const isLong = review.body.length > 220
return (
<li key={review.id} className="py-6">
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5">
{/* One image, not five icons — see the note in
testimonial-grid. Without the role the label is dropped. */}
<div
role="img"
aria-label={`${review.rating} out of 5`}
className="flex gap-0.5"
>
{Array.from({ length: 5 }, (_, i) => (
<Star
key={i}
aria-hidden
className={
i < review.rating
? 'h-3.5 w-3.5 fill-amber-400 text-amber-400'
: 'h-3.5 w-3.5 text-muted-foreground/30'
}
/>
))}
</div>
<span className="text-sm font-medium">{review.author}</span>
{/* The element that makes the rest of the page credible. */}
{review.verified ? (
<span className="inline-flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400">
<BadgeCheck aria-hidden className="h-3.5 w-3.5" />
Verified purchase
</span>
) : null}
<time dateTime={review.date} className="ms-auto text-xs text-muted-foreground">
{formatDate(review.date, locale)}
</time>
</div>
{review.title ? (
<h3 data-stress-ignore className="mt-2.5 font-semibold">{review.title}</h3>
) : null}
<p
id={`${uid}-${review.id}-body`}
className={`mt-1.5 text-sm leading-relaxed text-muted-foreground ${
isLong && !isExpanded ? 'line-clamp-3' : ''
}`}
>
{review.body}
</p>
{/* A control, not a fade that hides text with no way back. */}
{isLong ? (
<button
type="button"
onClick={() => toggleExpanded(review.id)}
aria-expanded={isExpanded}
/*
The paragraph, which is always in the document — this
control clamps text rather than unmounting it, so the
IDREF is unconditional here where the drawers and
listboxes elsewhere in the catalog have to guard it.
*/
aria-controls={`${uid}-${review.id}-body`}
className="mt-1.5 text-xs font-semibold hover:underline"
>
{isExpanded ? 'Show less' : 'Read more'}
</button>
) : null}
<div className="mt-3 flex flex-wrap items-center gap-4">
{review.context ? (
<span className="rounded-full bg-muted px-2.5 py-1 text-xs text-muted-foreground">
{review.context}
</span>
) : null}
<button
type="button"
onClick={() => toggleVote(review.id)}
aria-pressed={isVoted}
className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors ${
isVoted
? 'border-primary/40 bg-primary/10 text-primary'
: 'border-border/60 text-muted-foreground hover:text-foreground'
}`}
>
<ThumbsUp aria-hidden className="h-3.5 w-3.5" />
Helpful
{/* Optimistic — the count moves before the request lands. */}
<span className="tabular-nums">
{review.helpfulCount + (isVoted ? 1 : 0)}
</span>
</button>
</div>
</li>
)
})}
</ul>
</section>
)
}
bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.Drop it at components/review-list.tsx and import it where you need the section:
import { ReviewList } from '@/components/review-list'1 of this block’s props are simple enough to drive from here. Change them and the block below re-renders — it is the same component whose source is above, not a mock of it. Everything else it accepts is in the table underneath.
Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.
| Prop | Type | Default |
|---|---|---|
reviews | Review[] | DEFAULT_REVIEWS |
locale | string | 'en-GB' |
className | string | '' |
The same block rendered once to markup, wrapped as a file your framework compiles. Tailwind classes are framework-agnostic, so the design transfers intact — the behaviour does not.
This block is interactive. The markup below is its initial state with the event handlers stripped — you will need to re-wire the behaviour in your framework.
<!--
Customer Reviews — markup from the Hoverlab catalog.
This is the block rendered once to HTML and wrapped as a component
file. It is not a port of the React source: the Tailwind classes carry
the design, which is the part that took the work, and they are the same
in every framework.
This block is interactive in React and the handlers are NOT here.
Buttons, toggles and menus render in their initial state and do
nothing until you wire them up.
-->
<section id="_R_0_-reviews" class="w-full ">
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-border/60 pb-4">
<h2 class="text-lg font-bold tracking-tight">4 reviews</h2>
<div class="flex items-center gap-2">
<label for="_R_0_-review-sort" class="text-sm text-muted-foreground">Sort</label>
<select id="_R_0_-review-sort" class="rounded-xl border border-border/60 bg-background px-2.5 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-primary">
<option value="helpful" selected="">Most helpful</option>
<option value="recent">Most recent</option>
<option value="highest">Highest rated</option>
<option value="lowest">Lowest rated</option>
</select>
</div>
</div>
<ul class="divide-y divide-border/40">
<li class="py-6">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1.5">
<div role="img" aria-label="5 out of 5" class="flex gap-0.5">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
</div>
<span class="text-sm font-medium">Hannah T.</span>
<span class="inline-flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-badge-check h-3.5 w-3.5" aria-hidden="true"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"></path><path d="m9 12 2 2 4-4"></path></svg>Verified purchase</span>
<time dateTime="2026-07-28" class="ms-auto text-xs text-muted-foreground">28 July 2026</time>
</div>
<h3 data-stress-ignore="true" class="mt-2.5 font-semibold">Better than the price suggests</h3>
<p id="_R_0_-1-body" class="mt-1.5 text-sm leading-relaxed text-muted-foreground line-clamp-3">I bought this expecting to return it and it has become the thing I reach for three days a week. The knit is dense without being heavy, and after four washes there is no pilling at the cuffs where I would expect it first. Sizing is honest — I am usually a medium and the medium fits with room for a shirt underneath.</p>
<button type="button" aria-expanded="false" aria-controls="_R_0_-1-body" class="mt-1.5 text-xs font-semibold hover:underline">Read more</button>
<div class="mt-3 flex flex-wrap items-center gap-4">
<span class="rounded-full bg-muted px-2.5 py-1 text-xs text-muted-foreground">Bought M · usually M</span>
<button type="button" aria-pressed="false" class="inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors border-border/60 text-muted-foreground hover:text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-thumbs-up h-3.5 w-3.5" aria-hidden="true"><path d="M7 10v12"></path><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"></path></svg>Helpful<span class="tabular-nums">34</span></button>
</div>
</li>
<li class="py-6">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1.5">
<div role="img" aria-label="3 out of 5" class="flex gap-0.5">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 text-muted-foreground/30" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 text-muted-foreground/30" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
</div>
<span class="text-sm font-medium">Sam K.</span>
<time dateTime="2026-06-11" class="ms-auto text-xs text-muted-foreground">11 June 2026</time>
</div>
<h3 data-stress-ignore="true" class="mt-2.5 font-semibold">Nice but the colour is off</h3>
<p id="_R_0_-4-body" class="mt-1.5 text-sm leading-relaxed text-muted-foreground ">The stone reads much warmer in person than on screen — closer to a light camel. Lovely sweater, just not the colour I was picturing.</p>
<div class="mt-3 flex flex-wrap items-center gap-4">
<button type="button" aria-pressed="false" class="inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors border-border/60 text-muted-foreground hover:text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-thumbs-up h-3.5 w-3.5" aria-hidden="true"><path d="M7 10v12"></path><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"></path></svg>Helpful<span class="tabular-nums">21</span></button>
</div>
</li>
<li class="py-6">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1.5">
<div role="img" aria-label="4 out of 5" class="flex gap-0.5">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 text-muted-foreground/30" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
</div>
<span class="text-sm font-medium">Devin R.</span>
<span class="inline-flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-badge-check h-3.5 w-3.5" aria-hidden="true"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"></path><path d="m9 12 2 2 4-4"></path></svg>Verified purchase</span>
<time dateTime="2026-07-14" class="ms-auto text-xs text-muted-foreground">14 July 2026</time>
</div>
<h3 data-stress-ignore="true" class="mt-2.5 font-semibold">Great, but runs slightly long</h3>
<p id="_R_0_-2-body" class="mt-1.5 text-sm leading-relaxed text-muted-foreground ">No complaints about the material at all. The body is about an inch longer than I expected, which is fine untucked but worth knowing if you are between sizes.</p>
<div class="mt-3 flex flex-wrap items-center gap-4">
<span class="rounded-full bg-muted px-2.5 py-1 text-xs text-muted-foreground">Bought L · usually L</span>
<button type="button" aria-pressed="false" class="inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors border-border/60 text-muted-foreground hover:text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-thumbs-up h-3.5 w-3.5" aria-hidden="true"><path d="M7 10v12"></path><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"></path></svg>Helpful<span class="tabular-nums">12</span></button>
</div>
</li>
<li class="py-6">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1.5">
<div role="img" aria-label="5 out of 5" class="flex gap-0.5">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star h-3.5 w-3.5 fill-amber-400 text-amber-400" aria-hidden="true">
<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
</svg>
</div>
<span class="text-sm font-medium">Priya M.</span>
<span class="inline-flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-badge-check h-3.5 w-3.5" aria-hidden="true"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"></path><path d="m9 12 2 2 4-4"></path></svg>Verified purchase</span>
<time dateTime="2026-06-30" class="ms-auto text-xs text-muted-foreground">30 June 2026</time>
</div>
<h3 data-stress-ignore="true" class="mt-2.5 font-semibold">Third one I have bought</h3>
<p id="_R_0_-3-body" class="mt-1.5 text-sm leading-relaxed text-muted-foreground ">Two in stone, one in navy. They hold their shape better than anything else I own at twice the price.</p>
<div class="mt-3 flex flex-wrap items-center gap-4">
<button type="button" aria-pressed="false" class="inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors border-border/60 text-muted-foreground hover:text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-thumbs-up h-3.5 w-3.5" aria-hidden="true"><path d="M7 10v12"></path><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"></path></svg>Helpful<span class="tabular-nums">8</span></button>
</div>
</li>
</ul>
</section>
What each framework gets across the whole catalog — effects convert properly; this rung is markup.
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.
Want the whole screen instead of this one section? Open a page and copy it entire.
All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.
Browse Orders & ReviewsCopying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.
The thank-you screen built to prevent two support emails: a selectable order number and a delivery date range rather than "soon".
Past orders with stacked item thumbnails and worded statuses — people recognise what they bought long before the order number.
The fortnight between buying and reviewing, answered properly: an arrival range rather than a date, exceptions above the timeline, and a copyable carrier reference.
The form that produces the reviews the other blocks display — a real radio group for stars, and a prompt that changes with the rating because one star and five need different questions.
Order tracking for the guest who never made an account, which is most of them.
The rating summary with the numbers that make an average trustworthy — the count, the spread and the verified share.