Receipt Cart
A shopping cart built on the thermal-receipt metaphor — line items print in one row at a time, the perforated paper edge extends, the subtotal and free-shipping progress at the tail roll to their new values, and removing a row strikes it through and tears it out.
npx shadcn@latest add https://webberui.com/r/receipt-cart.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ReceiptCart />
Installation
npx shadcn@latest add https://webberui.com/r/receipt-cart.jsonOr, once registries are configured in components.json, install it as @webberui/receipt-cart.
Usage
Data-driven — just pass an array of line items. Both controlled (items + onItemsChange) and uncontrolled (defaultItems) modes are supported:
import { ReceiptCart } from "@/components/ui/receipt-cart";
<ReceiptCart
defaultItems={[
{ id: "a", name: "Pour-over drip coffee bags", price: 12.5, quantity: 2 },
{ id: "b", name: "Ceramic mug", price: 18, note: "Matte charcoal" },
]}
currency="$"
freeShippingThreshold={75}
shippingFee={5}
taxRate={0.08}
onRemove={(removed) => console.log("torn out", removed.name)}
/>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | ReceiptCartItem[] | — | Controlled mode: the item array supplied from the outside |
defaultItems | ReceiptCartItem[] | [] | Uncontrolled mode: the initial item array |
onItemsChange | (items) => void | — | Returns the complete array after the items change |
onRemove | (removed, remaining) => void | — | Fires after a row is torn out |
currency | string | "$" | Currency prefix symbol |
freeShippingThreshold | number | — | Free shipping threshold; when provided, a progress bar is shown at the paper tail |
shippingFee | number | 0 | Shipping fee while below the threshold |
taxRate | number | — | Tax rate (0–1); when provided, tax is added |
storeName | string | "WEBBER MART" | Store name in the receipt header |
subtitle | string | "CUSTOMER RECEIPT" | Header subtitle |
meta | ReactNode | — | Row of date / transaction information below the header |
orderNumber | string | — | Order number text below the barcode |
removable | boolean | true | Whether to show a remove button on each row |
printStagger | number | 0.12 | Interval between each row of the printed reveal (seconds) |
ReceiptCartItem: { id, name, price, quantity?, note? }.
How it works
- Line items print in one row at a time, like a receipt printer: the whole row fades in and moves down while its content develops from top to bottom through
clip-path, withprintStaggercontrolling the rhythm - The perforated paper edge tiles triangular teeth with an SVG
pattern, filled to match the paper's base color, so the paper extends downward naturally as items are added - The subtotal, tax, shipping, and free-shipping progress at the paper tail roll smoothly on a spring; on reaching the threshold, the shipping row switches to "FREE" and the progress bar fills
- Removing an item first strikes a horizontal line through that row, and only tears it out once the strike is complete (the row height collapses and it slides right and fades out), while the remaining rows smoothly fill the gap
Accessibility
- The receipt container carries
role="region"and anaria-label, and the free-shipping progress bar is arole="progressbar"providingaria-valuenow/min/max - Each remove button carries an
aria-labelwith the matching item name, is keyboard focusable, and has a clear focus ring onfocus-visible - When the user has "reduce motion" enabled at the system level, the print and roll animations are disabled, removal skips the strike-through and tears out directly, and amounts show their final value immediately
Product Drop Countdown
A limited-release drop board with a built-in flip countdown, a CTA that unlocks at launch, and a three-state machine ending in a restock notification form once it sells out.
Versus Tray
Check off products and a comparison tray slides out from the bottom; the thumbnails FLIP into a full-width spec table that scans row by row and marks the winner of each one.