WebberUI

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.

Loading preview…
npx shadcn@latest add https://webberui.com/r/receipt-cart.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

60
5
0.05
<ReceiptCart />

Installation

npx shadcn@latest add https://webberui.com/r/receipt-cart.json

Or, 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

PropTypeDefaultDescription
itemsReceiptCartItem[]Controlled mode: the item array supplied from the outside
defaultItemsReceiptCartItem[][]Uncontrolled mode: the initial item array
onItemsChange(items) => voidReturns the complete array after the items change
onRemove(removed, remaining) => voidFires after a row is torn out
currencystring"$"Currency prefix symbol
freeShippingThresholdnumberFree shipping threshold; when provided, a progress bar is shown at the paper tail
shippingFeenumber0Shipping fee while below the threshold
taxRatenumberTax rate (0–1); when provided, tax is added
storeNamestring"WEBBER MART"Store name in the receipt header
subtitlestring"CUSTOMER RECEIPT"Header subtitle
metaReactNodeRow of date / transaction information below the header
orderNumberstringOrder number text below the barcode
removablebooleantrueWhether to show a remove button on each row
printStaggernumber0.12Interval 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, with printStagger controlling 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 an aria-label, and the free-shipping progress bar is a role="progressbar" providing aria-valuenow/min/max
  • Each remove button carries an aria-label with the matching item name, is keyboard focusable, and has a clear focus ring on focus-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

On this page