Commerce & Checkout

Receipt Printer

A receipt that prints after checkout like a thermal printer feeding paper line by line: processing, printing and complete stages, a serrated paper edge, tear-off and replay.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

The moment a payment succeeds, let the receipt come out notch by notch like a thermal receipt printer. The component has three stages: while processing, the indicator light on the printer body pulses and the small screen shows the status; while printing, the paper feeds out of the slot in 20 stepped jolts (or in one smooth motion); once complete, the receipt can be torn off with a button or by dragging it down, and it drops away with a random tilt. The serrated bottom edge is generated with clip-path and the tooth count is adjustable; the body and paper textures are built entirely from CSS gradients and inline SVG noise, with no external images. The receipt content is just children, so you decide how items, tax and the order number are laid out.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add "https://webberui.com/r/receipt-printer.en.json?t=<install token>"

New project or installation trouble? Start here →

Playground

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

1.75
40
Active edited usageDefault props
<ReceiptPrinter/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add "https://webberui.com/r/receipt-printer.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/receipt-printer.

Usage

import * as React from "react";
import {
  ReceiptPrinter,
  type ReceiptStage,
} from "@/components/ui/receipt-printer";

export function CheckoutReceipt() {
  const [stage, setStage] = React.useState<ReceiptStage>("idle");

  async function pay() {
    setStage("processing");
    await chargeCard(); // your payment API
    setStage("printing");
  }

  return (
    <>
      <button type="button" onClick={pay}>
        Confirm payment
      </button>
      <ReceiptPrinter stage={stage} onStageChange={setStage}>
        <p className="text-center font-bold">Timeworn Goods</p>
        <dl className="mt-2 space-y-1 border-t border-dashed pt-2">
          <div className="flex justify-between">
            <dt>Annual plan</dt>
            <dd>$96.00</dd>
          </div>
          <div className="flex justify-between font-semibold">
            <dt>Total</dt>
            <dd>$100.80</dd>
          </div>
        </dl>
      </ReceiptPrinter>
    </>
  );
}

If you don't need to wire it to a payment flow, use uncontrolled mode: after mounting it waits processingMs, prints on its own, and the ref's replay() runs it again.

const ref = React.useRef<ReceiptPrinterHandle>(null);

<ReceiptPrinter ref={ref} mode="smooth" duration={2.4} teeth={28}>
  {/* receipt details */}
</ReceiptPrinter>;

ref.current?.replay();

For a custom layout (for example, placing the status line elsewhere), use the compound parts:

<ReceiptPrinter.Root stage={stage} onStageChange={setStage}>
  <ReceiptPrinter.Machine>
    <ReceiptPrinter.Header>TP-58</ReceiptPrinter.Header>
    <ReceiptPrinter.Screen />
  </ReceiptPrinter.Machine>
  <ReceiptPrinter.Output>
    <ReceiptPrinter.Paper>{/* receipt details */}</ReceiptPrinter.Paper>
  </ReceiptPrinter.Output>
  <ReceiptPrinter.Status className="mt-3" />
</ReceiptPrinter.Root>

Props

ReceiptPrinter and ReceiptPrinter.Root take the same props (children of ReceiptPrinter go inside the paper; children of Root are the parts you compose).

PropTypeDefaultDescription
stage"idle" | "processing" | "printing" | "complete"—Controlled stage; managed internally when omitted
defaultStageReceiptStage"processing"Initial stage in uncontrolled mode
onStageChange(stage: ReceiptStage) => void—Called on stage changes; the end of the paper feed reports "complete", and in controlled mode the parent decides whether to apply it
mode"stepped" | "smooth""stepped"stepped feeds line by line in 20 jolts; smooth feeds in one ease-in-out motion
durationnumber1.75Total feed length in seconds; reads the CSS variable --wb-receipt-duration when omitted
teethnumber40Number of teeth on the serrated bottom edge; tooth depth is fixed at 4px
processingMsnumber1200Milliseconds spent in processing when uncontrolled, before moving on to printing
tearablebooleantrueWhether the receipt can be torn off when complete (button, or dragging down more than 60px)
onTear() => void—Called after the tear-off animation ends
labelsReceiptPrinterLabels—Override interface and screen-reader copy
grainbooleanfalseOverlay an extra grain layer on the body and paper
refReact.Ref<ReceiptPrinterHandle>—Exposes replay(): pulls the receipt back in and returns to processing
classNamestring—Forwarded to the outermost container

The Machine, Header, Output and Paper parts take children and className; Screen and Status take only className.

ReceiptPrinterLabels

FieldTypeDefaultDescription
devicestring"出單機" (Printer)Device name on top of the body (shown when Header has no children)
idlestring"待命" (Ready)Status text for the idle stage
processingstring"處理中…" (Processing…)Status text for the processing stage
printingstring"列印收據…" (Printing receipt…)Status text for the printing stage
completestring"完成" (Done)Status text for the complete stage
srProcessingstring"正在處理付款" (Processing payment)Screen-reader announcement: processing
srPrintingstring"正在列印收據" (Printing receipt)Screen-reader announcement: printing
srCompletestring"收據已完成" (Receipt complete)Screen-reader announcement: complete
srTornstring"收據已撕下" (Receipt torn off)Screen-reader announcement: torn off
tearstring"撕下收據" (Tear off receipt)Tear button text
paperstring"收據" (Receipt)aria-label of the paper section

The ReceiptStage, ReceiptFeedMode, ReceiptPrinterHandle, ReceiptPrinterProps and ReceiptPrinterRootProps types are also exported by name.

How it works

  • Stepped feed: stepped mode splits the 0→100% feed into 20 steps — 41 keyframe values alternating "advance, hold", with evenly spaced times and linear ease, so advancing and holding each take half the time. It reads as a thermal printer pushing paper out one notch at a time
  • Paper slot: the paper starts at translateY(-100%), hidden inside the slot, and moves to 0 while printing. The slot uses clip-path: inset() to clip only the top edge, so the tilt, fall and shadow of the tear-off are not cut off
  • Controlled and uncontrolled: uncontrolled, it waits processingMs in processing, prints, and completes on its own; controlled, the component only reports onStageChange("complete") when the feed ends, and the parent decides when to move from processing to printing (usually once the payment API responds with success)
  • Rhythm variables: --wb-receipt-duration (total feed length), --wb-receipt-accent (indicator and complete icon, default #34d399), --wb-receipt-processing (processing indicator, default #fbbf24), --wb-receipt-screen (screen text, default #bef264)
  • Tear-off: the tilt is picked at random between −8° and 8° at the moment of tearing, and the paper moves down 120% and fades out over 500ms ease-in; it resets when the stage returns to processing or replay() is called
  • Dark mode: the paper stays a light paper color (neutral-100) and the body is dark already, so it looks like a real receipt printer in both themes

Accessibility

  • Root contains an sr-only region with role="status" and aria-live="polite" that announces "Processing payment", "Printing receipt" and "Receipt complete" per stage, and "Receipt torn off" after a tear-off; the visual text in Screen and Status is aria-hidden to avoid double announcements
  • The paper is a section with aria-label="Receipt"; before printing and after tearing it is aria-hidden and inert. Marking up the details with dl or table is recommended
  • "Tear off receipt" is a real button: Tab-focusable, triggered with Enter or Space, with a focus-visible ring; drag-to-tear is only an extra pointer interaction
  • When the user has turned on reduce motion in their system settings, there is no paper feed and no jolts — entering printing shows the full receipt and the complete status right away; status text only fades without moving, the indicator does not pulse, and the tear-off becomes a fade