Skip to content
Commerce block

Order Confirmation

The thank-you screen built to prevent two support emails: a selectable order number and a delivery date range rather than "soon".

212 lineslucide-reactAdded 8 Aug 2026Updated 2 Sept 2026
  • order confirmation
  • thank you
  • receipt
  • checkout
  • success

What's included

  • components/order-confirmation.tsx
  • Needs lucide-react

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add order-confirmation

Or over MCP, from your editor's agent — no account needed.

License

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.

Was this useful?

Start a page with this section — add more, order them, and leave with the page source.

Preview

Thanks — your order is confirmed

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.

Delivering to

Ada Lovelace12 Marchmont StreetLondonWC1N 1AGUnited Kingdom

Payment

Subtotal
£219.00
Discount
−£21.90
Delivery
Free
Total
£197.10

2 items

  • 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.

Source

components/order-confirmation.tsx
/**
 * <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>
  )
}

Before you paste

  • Styling is Tailwind utility classes on semantic tokens (bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.
  • Install: npm i lucide-react
  • Every prop has a default, so it renders standalone before you wire it up.

Where it goes

Drop it at components/order-confirmation.tsx and import it where you need the section:

import { OrderConfirmation } from '@/components/order-confirmation'

Customize

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.

Props

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.

PropTypeDefault
orderNumberstring'ORD-4821-9930'
emailstring'you@example.com'
linesConfirmationLine[]DEFAULT_LINES
shippingnumber0
discountnumber2190
currencystring'GBP'
localestring'en-GB'
deliveryFromISO dates bounding the delivery estimate.string'2026-08-14'
deliveryTostring'2026-08-18'
addressstring[]['Ada Lovelace', '12 Marchmont Street', 'Lond…
classNamestring''

Not using React?

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.html
<!--
  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>
  • This is rendered HTML, not a translation of the React source. The Tailwind classes carry the design and work in any framework.
  • It is one frame: the component in its initial state, with no props applied beyond the defaults.
  • Requires Tailwind, and the design tokens the classes reference (bg-card, text-muted-foreground, and so on). The template ZIPs ship a globals.css that defines them.

What each framework gets across the whole catalog — effects convert properly; this rung is markup.

For AI

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.

See the prompt

Used in these pages

Want the whole screen instead of this one section? Open a page and copy it entire.

6 more blocks in Orders & Reviews

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 & Reviews

Shipping one commercially

Copying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.

  • A commercial licence for everything in the catalog
  • Unlimited bundle exports, in Vue, Svelte and Tailwind
  • One payment — no subscription, nothing to renew
Pro — $79 once

More Orders & Reviews blocks

View category

Order History

Past orders with stacked item thumbnails and worded statuses — people recognise what they bought long before the order number.

Orders & Reviews195 lines1 dep
Open the full page for this block

Customer Reviews

Sortable reviews with verified-purchase badges, optimistic helpful votes and a real expand control instead of a fade that hides text.

Orders & Reviews274 lines1 dep
Open the full page for this block

Order Tracking Timeline

The fortnight between buying and reviewing, answered properly: an arrival range rather than a date, exceptions above the timeline, and a copyable carrier reference.

Orders & Reviews258 lines1 dep
Open the full page for this block

Write a Review

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.

Orders & Reviews249 lines1 dep
Open the full page for this block

Order Lookup Form

Order tracking for the guest who never made an account, which is most of them.

Orders & Reviews176 linesNo deps
Open the full page for this block

Review Distribution Band

The rating summary with the numbers that make an average trustworthy — the count, the spread and the verified share.

Orders & Reviews125 linesNo deps