Skip to content
LofiStack Gallery
Type: cardWeek 03

Receipt Card

An order confirmation that looks like a real thermal receipt. It slides out of a printer slot, then a PAID, PENDING or REFUNDED stamp thumps on. You pass the items and it does the math: subtotal, discount, tax on the discounted amount, tip and total, all formatted for any currency and locale. The torn edge is a pure-CSS mask, and the barcode is generated from the order id, so each order looks unique.

View on GitHub

Lofi Coffee Co.

Order #1042Oct 9, 2026 · 9:41 AM


ItemQuantityAmount
Oat latteExtra shotQuantity ×2$9.00
Almond croissantQuantity ×1$3.75
Cold brewLess iceQuantity ×1$4.25

Subtotal
$17.00
LOFI10
−$2.00
Tax (8%)
$1.20
Tip
$2.00
Total
$18.20

Paid with Visa •••• 4242

Status: Paid


#1042

Thanks for stopping by!

Usage

usage.tsx
import { ReceiptCard } from "@/components/gallery/receipt-card";

<ReceiptCard
  merchant="Lofi Coffee Co."
  orderId="#1042"
  date="Oct 9, 2026 · 9:41 AM"
  items={[
    { name: "Oat latte", quantity: 2, price: 4.5, note: "Extra shot" },
    { name: "Almond croissant", price: 3.75 },
  ]}
  discount={{ label: "LOFI10", amount: 2 }}
  taxRate={0.08}
  tip={2}
  paymentMethod="Visa •••• 4242"
  status="paid"
/>

States

Printing (slides out of the slot) · Paid / Pending / Refunded stamp · With discount, tax and tip · Empty order · Hover (deeper shadow) · No printer slot · Static (animate=false) · Reduced motion (no animation)

Lofi Coffee

Order #1001


ItemQuantityAmount
MatchaQuantity ×1$5.00

Subtotal
$5.00
Tax (8%)
$0.40
Total
$5.40

Status: Paid


#1001

Paid

Lofi Coffee

Order #1002


ItemQuantityAmount
MatchaQuantity ×1$5.00

Subtotal
$5.00
Total
$5.00

Status: Pending


#1002

Pending

Lofi Coffee

Order #1003


ItemQuantityAmount
MatchaQuantity ×1$5.00

Subtotal
$5.00
Total
$5.00

Status: Refunded


#1003

Refunded

Lofi Coffee

Order #1004


No items


Subtotal
$0.00
Total
$0.00

#1004

Empty order

Props

  • merchant*

    string

    Shop name in the header.

  • items*

    { name; price; quantity?; note? }[]

    Line items. Quantity defaults to 1.

  • orderId / date

    string

    Shown under the merchant name. orderId also seeds the barcode.

  • taxRate= 0

    number

    Applied after discounts, e.g. 0.08 = 8%.

  • discount

    { label; amount }

    Discount line, capped at the subtotal.

  • tip= 0

    number

    Added after tax.

  • currency / locale= "USD" / "en-US"

    string

    Passed to Intl.NumberFormat for every amount.

  • paymentMethod

    string

    e.g. “Visa •••• 4242”.

  • status

    "paid" | "pending" | "refunded"

    Adds the rubber stamp.

  • footer= "Thank you!"

    string

    Closing line. Empty string hides it.

  • logo

    ReactNode

    Icon above the merchant name.

  • barcodeValue= orderId

    string

    Text encoded into the decorative barcode.

  • animate= true

    boolean

    Print-out + stamp animation on mount.

  • printer= true

    boolean

    Show the printer slot the receipt comes out of.

  • className

    string

    Extra classes for the wrapper.

Accessibility

  • An <article> labelled “Receipt from …”.
  • Items are a real table with screen-reader-only column headers, and totals are a definition list.
  • The stamp includes hidden text, “Status: Paid”.
  • The barcode graphic is decorative (aria-hidden); its value is printed as text.
  • The paper stays light in dark mode with dark text, so contrast is always high.
  • The print and stamp animations are disabled under prefers-reduced-motion.

Build prompt

The final prompt used to build this component
Build a reusable React 19 + TypeScript + Tailwind v4 component called ReceiptCard: an order confirmation styled as a thermal paper receipt.

Props (typed): merchant, items ({ name, price, quantity?, note? }[]), orderId, date, taxRate, discount ({ label, amount }), tip, currency, locale, paymentMethod, status ("paid" | "pending" | "refunded"), footer, logo, barcodeValue, animate, printer, className.

Math: subtotal = Σ price × quantity. Discount is capped at the subtotal. Tax = (subtotal − discount) × taxRate. Total = subtotal − discount + tax + tip. Format every amount with Intl.NumberFormat(locale, { style: "currency", currency }).

Look:
- Off-white paper (#fffdf7) with faint ruled lines, a monospace font and dashed dividers. Merchant in spaced uppercase, then order id · date.
- Bottom torn zig-zag edge made with a CSS mask (conic-gradient(from -45deg at bottom, …) repeated every 14px). A drop-shadow on the wrapper follows the torn shape, and gets deeper on hover.
- Items in a real <table> (sr-only header), totals in a <dl> with a bold Total row, optional "Paid with …".
- A decorative SVG barcode whose bar widths come from an FNV-style hash of the order id (deterministic), with the value printed below.
- Status stamp: rotated rubber stamp (Paid green / Pending amber / Refunded red, thick border, mix-blend-multiply) with sr-only "Status:".

Animation (keyframes via React 19 <style href precedence>): a dark printer slot bar at the top. The paper slides down out of it with a slight overshoot (1.5s), then the stamp thumps in (scale 2.2 → 1). animate=false or prefers-reduced-motion shows it instantly. Empty items show "No items". No dependencies.