Order History
Past orders with stacked item thumbnails and worded statuses — people recognise what they bought long before the order number.
The thank-you screen built to prevent two support emails: a selectable order number and a delivery date range rather than "soon".
components/order-confirmation.tsxnpx hoverlab add order-confirmation
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.
A confirmation is on its way to you@example.com
Order numberORD-4821-9930
Estimated delivery
–
We will email a tracking link as soon as it ships.
Merino Crew Sweater
Stone · M
×1
£89.00
Cotton Oxford Shirt
Sky · L
×2
£130.00
Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.
/**
* <OrderConfirmation> — the thank-you screen.
*
* The job of this page is to stop the two support emails it would otherwise
* generate: "did my order go through" and "when will it arrive". So the
* order number is large and copyable, the delivery estimate is a date range
* rather than "soon", and the confirmation-email address is shown so a typo
* is caught here rather than after three days of silence.
*
* No confetti, no animation. This screen is often reached by someone
* checking a purchase they are anxious about, and it should read as a
* receipt rather than a celebration.
*
* Server component.
*/
import * as React from 'react'
import { CircleCheck, Package, Truck, Mail } from 'lucide-react'
export interface ConfirmationLine {
id: string
name: string
variant?: string
price: number
quantity: number
swatch?: string
}
export interface OrderConfirmationProps {
orderNumber?: string
email?: string
lines?: ConfirmationLine[]
shipping?: number
discount?: number
currency?: string
locale?: string
/** ISO dates bounding the delivery estimate. */
deliveryFrom?: string
deliveryTo?: string
address?: string[]
className?: string
}
const DEFAULT_LINES: ConfirmationLine[] = [
{ id: '1', name: 'Merino Crew Sweater', variant: 'Stone · M', price: 8900, quantity: 1, swatch: 'from-stone-300 to-stone-500' },
{ id: '2', name: 'Cotton Oxford Shirt', variant: 'Sky · L', price: 6500, quantity: 2, swatch: 'from-sky-200 to-sky-400' },
]
function formatPrice(minor: number, currency = 'GBP', locale = 'en-GB'): string {
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(minor / 100)
}
/** Pinned to UTC — a bare `new Date('2026-08-14')` renders as the 13th west of Greenwich. */
function formatDate(iso: string, locale = 'en-GB'): string {
return new Date(`${iso}T00:00:00Z`).toLocaleDateString(locale, {
weekday: 'short',
day: 'numeric',
month: 'long',
timeZone: 'UTC',
})
}
export function OrderConfirmation({
orderNumber = 'ORD-4821-9930',
email = 'you@example.com',
lines = DEFAULT_LINES,
shipping = 0,
discount = 2190,
currency = 'GBP',
locale = 'en-GB',
deliveryFrom = '2026-08-14',
deliveryTo = '2026-08-18',
address = ['Ada Lovelace', '12 Marchmont Street', 'London', 'WC1N 1AG', 'United Kingdom'],
className = '',
}: OrderConfirmationProps) {
const subtotal = lines.reduce((n, l) => n + l.price * l.quantity, 0)
const total = subtotal - discount + shipping
return (
<div className={`mx-auto w-full max-w-2xl px-6 py-12 ${className}`}>
<div className="text-center">
<span className="inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-emerald-500/15 text-emerald-600 dark:text-emerald-400">
<CircleCheck className="h-7 w-7" />
</span>
<h1 className="mt-5 text-balance text-3xl font-extrabold tracking-tight">
Thanks — your order is confirmed
</h1>
<p className="mt-3 flex flex-wrap items-center justify-center gap-1.5 text-sm text-muted-foreground">
<Mail aria-hidden className="h-4 w-4" />
A confirmation is on its way to{' '}
<span className="font-medium text-foreground">{email}</span>
</p>
{/* Selectable, so it can be pasted into a support email. */}
<p className="mt-5 inline-block rounded-xl border border-border/60 bg-card/60 px-4 py-2.5">
<span className="block text-xs text-muted-foreground">Order number</span>
<span className="select-all font-mono text-lg font-semibold tracking-tight">
{orderNumber}
</span>
</p>
</div>
{/* A date range, not "soon". */}
<div className="mt-8 flex items-start gap-3 rounded-2xl border border-border/60 bg-card/60 p-5">
<Truck aria-hidden className="mt-0.5 h-5 w-5 shrink-0 text-primary" />
<div>
<p className="font-medium">Estimated delivery</p>
<p className="mt-0.5 text-sm text-muted-foreground">
<time dateTime={deliveryFrom}>{formatDate(deliveryFrom, locale)}</time>
{' – '}
<time dateTime={deliveryTo}>{formatDate(deliveryTo, locale)}</time>
</p>
<p className="mt-2 text-sm text-muted-foreground">
We will email a tracking link as soon as it ships.
</p>
</div>
</div>
<div className="mt-6 grid gap-6 sm:grid-cols-2">
<section className="min-w-0 rounded-2xl border border-border/60 bg-card/60 p-5">
<h2 className="flex items-center gap-2 text-sm font-semibold">
<Package aria-hidden className="h-4 w-4" />
Delivering to
</h2>
<address className="mt-2 text-sm not-italic leading-relaxed text-muted-foreground">
{/* Keyed by position: an address is a fixed list of display
lines, and two of them can legitimately read the same (or be
blank). Keying by content drops the duplicate. */}
{address.map((line, i) => (
<span key={i} className="block break-words">
{line}
</span>
))}
</address>
</section>
<section className="min-w-0 rounded-2xl border border-border/60 bg-card/60 p-5">
<h2 className="text-sm font-semibold">Payment</h2>
<dl className="mt-2 space-y-1.5 text-sm">
<div className="flex justify-between gap-3">
<dt className="min-w-0 break-words text-muted-foreground">Subtotal</dt>
<dd className="shrink-0 tabular-nums">{formatPrice(subtotal, currency, locale)}</dd>
</div>
{discount > 0 ? (
<div className="flex justify-between gap-3 text-emerald-600 dark:text-emerald-400">
<dt className="min-w-0 break-words">Discount</dt>
<dd className="shrink-0 tabular-nums">−{formatPrice(discount, currency, locale)}</dd>
</div>
) : null}
<div className="flex justify-between gap-3">
<dt className="min-w-0 break-words text-muted-foreground">Delivery</dt>
<dd className="min-w-0 shrink-0 break-words text-end tabular-nums">
{shipping === 0 ? 'Free' : formatPrice(shipping, currency, locale)}
</dd>
</div>
<div className="flex justify-between gap-3 border-t border-border/60 pt-1.5 font-bold">
<dt className="min-w-0 break-words">Total</dt>
<dd className="shrink-0 tabular-nums">{formatPrice(total, currency, locale)}</dd>
</div>
</dl>
</section>
</div>
<section className="mt-6 rounded-2xl border border-border/60 bg-card/60 p-5">
<h2 className="text-sm font-semibold">
{lines.length} {lines.length === 1 ? 'item' : 'items'}
</h2>
<ul className="mt-3 divide-y divide-border/40">
{lines.map((line) => (
<li key={line.id} className="flex items-center gap-3 py-3">
<div className="h-14 w-11 shrink-0 overflow-hidden rounded-lg border border-border/60 bg-muted">
<div
aria-hidden
className={`h-full w-full bg-gradient-to-br ${line.swatch ?? 'from-muted to-muted-foreground/20'}`}
/>
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{line.name}</p>
{line.variant ? (
<p className="text-xs text-muted-foreground">{line.variant}</p>
) : null}
</div>
<p className="shrink-0 text-sm text-muted-foreground">×{line.quantity}</p>
<p className="min-w-20 shrink-0 text-end text-sm font-medium tabular-nums">
{formatPrice(line.price * line.quantity, currency, locale)}
</p>
</li>
))}
</ul>
</section>
<div className="mt-8 flex flex-wrap justify-center gap-3">
<a
href="/orders"
className="rounded-xl bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary/90"
>
Track this order
</a>
<a
href="/"
className="rounded-xl border border-border/60 bg-background px-5 py-2.5 text-sm font-medium transition-colors hover:bg-muted"
>
Continue shopping
</a>
</div>
</div>
)
}
bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.Drop it at components/order-confirmation.tsx and import it where you need the section:
import { OrderConfirmation } from '@/components/order-confirmation'8 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 |
|---|---|---|
orderNumber | string | 'ORD-4821-9930' |
email | string | 'you@example.com' |
lines | ConfirmationLine[] | DEFAULT_LINES |
shipping | number | 0 |
discount | number | 2190 |
currency | string | 'GBP' |
locale | string | 'en-GB' |
deliveryFromISO dates bounding the delivery estimate. | string | '2026-08-14' |
deliveryTo | string | '2026-08-18' |
address | string[] | ['Ada Lovelace', '12 Marchmont Street', 'Lond… |
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.
<!--
Order Confirmation — 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 has no interactive behaviour, so nothing is missing.
-->
<div class="mx-auto w-full max-w-2xl px-6 py-12 ">
<div class="text-center">
<span class="inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-emerald-500/15 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-circle-check h-7 w-7" aria-hidden="true">
<circle cx="12" cy="12" r="10"></circle>
<path d="m9 12 2 2 4-4"></path>
</svg>
</span>
<h1 class="mt-5 text-balance text-3xl font-extrabold tracking-tight">Thanks — your order is confirmed</h1>
<p class="mt-3 flex flex-wrap items-center justify-center gap-1.5 text-sm text-muted-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-mail h-4 w-4" aria-hidden="true"><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"></path><rect x="2" y="4" width="20" height="16" rx="2"></rect></svg>A confirmation is on its way to <span class="font-medium text-foreground">you@example.com</span></p>
<p class="mt-5 inline-block rounded-xl border border-border/60 bg-card/60 px-4 py-2.5">
<span class="block text-xs text-muted-foreground">Order number</span>
<span class="select-all font-mono text-lg font-semibold tracking-tight">ORD-4821-9930</span>
</p>
</div>
<div class="mt-8 flex items-start gap-3 rounded-2xl border border-border/60 bg-card/60 p-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-truck mt-0.5 h-5 w-5 shrink-0 text-primary" aria-hidden="true">
<path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"></path>
<path d="M15 18H9"></path>
<path d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"></path>
<circle cx="17" cy="18" r="2"></circle>
<circle cx="7" cy="18" r="2"></circle>
</svg>
<div>
<p class="font-medium">Estimated delivery</p>
<p class="mt-0.5 text-sm text-muted-foreground"><time dateTime="2026-08-14">Fri 14 August</time> – <time dateTime="2026-08-18">Tue 18 August</time></p>
<p class="mt-2 text-sm text-muted-foreground">We will email a tracking link as soon as it ships.</p>
</div>
</div>
<div class="mt-6 grid gap-6 sm:grid-cols-2">
<section class="min-w-0 rounded-2xl border border-border/60 bg-card/60 p-5">
<h2 class="flex items-center gap-2 text-sm font-semibold"><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-package h-4 w-4" aria-hidden="true"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"></path><path d="M12 22V12"></path><polyline points="3.29 7 12 12 20.71 7"></polyline><path d="m7.5 4.27 9 5.15"></path></svg>Delivering to</h2>
<address class="mt-2 text-sm not-italic leading-relaxed text-muted-foreground">
<span class="block break-words">Ada Lovelace</span>
<span class="block break-words">12 Marchmont Street</span>
<span class="block break-words">London</span>
<span class="block break-words">WC1N 1AG</span>
<span class="block break-words">United Kingdom</span>
</address>
</section>
<section class="min-w-0 rounded-2xl border border-border/60 bg-card/60 p-5">
<h2 class="text-sm font-semibold">Payment</h2>
<dl class="mt-2 space-y-1.5 text-sm">
<div class="flex justify-between gap-3">
<dt class="min-w-0 break-words text-muted-foreground">Subtotal</dt>
<dd class="shrink-0 tabular-nums">£219.00</dd>
</div>
<div class="flex justify-between gap-3 text-emerald-600 dark:text-emerald-400">
<dt class="min-w-0 break-words">Discount</dt>
<dd class="shrink-0 tabular-nums">−£21.90</dd>
</div>
<div class="flex justify-between gap-3">
<dt class="min-w-0 break-words text-muted-foreground">Delivery</dt>
<dd class="min-w-0 shrink-0 break-words text-end tabular-nums">Free</dd>
</div>
<div class="flex justify-between gap-3 border-t border-border/60 pt-1.5 font-bold">
<dt class="min-w-0 break-words">Total</dt>
<dd class="shrink-0 tabular-nums">£197.10</dd>
</div>
</dl>
</section>
</div>
<section class="mt-6 rounded-2xl border border-border/60 bg-card/60 p-5">
<h2 class="text-sm font-semibold">2 items</h2>
<ul class="mt-3 divide-y divide-border/40">
<li class="flex items-center gap-3 py-3">
<div class="h-14 w-11 shrink-0 overflow-hidden rounded-lg border border-border/60 bg-muted">
<div aria-hidden="true" class="h-full w-full bg-gradient-to-br from-stone-300 to-stone-500"></div>
</div>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium">Merino Crew Sweater</p>
<p class="text-xs text-muted-foreground">Stone · M</p>
</div>
<p class="shrink-0 text-sm text-muted-foreground">×1</p>
<p class="min-w-20 shrink-0 text-end text-sm font-medium tabular-nums">£89.00</p>
</li>
<li class="flex items-center gap-3 py-3">
<div class="h-14 w-11 shrink-0 overflow-hidden rounded-lg border border-border/60 bg-muted">
<div aria-hidden="true" class="h-full w-full bg-gradient-to-br from-sky-200 to-sky-400"></div>
</div>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium">Cotton Oxford Shirt</p>
<p class="text-xs text-muted-foreground">Sky · L</p>
</div>
<p class="shrink-0 text-sm text-muted-foreground">×2</p>
<p class="min-w-20 shrink-0 text-end text-sm font-medium tabular-nums">£130.00</p>
</li>
</ul>
</section>
<div class="mt-8 flex flex-wrap justify-center gap-3">
<a href="/orders" class="rounded-xl bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary/90">Track this order</a>
<a href="/" class="rounded-xl border border-border/60 bg-background px-5 py-2.5 text-sm font-medium transition-colors hover:bg-muted">Continue shopping</a>
</div>
</div>
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.
Past orders with stacked item thumbnails and worded statuses — people recognise what they bought long before the order number.
Sortable reviews with verified-purchase badges, optimistic helpful votes and a real expand control instead of a fade that hides text.
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.