Data

Receipt

A narrow till-style receipt in a monospace face: shop, date, lines, totals, payment and change, a footer and a torn edge.

#document#commerce#price#print#no-js

Demo

Preview
Corner Café

12 Bridge Street, Porto

+351 22 000 0000

Date
Receipt
0042
Served by
Rui
Table
4
2 × Flat white @ €3.50€7.00
Croissant€2.80
1 × Orange juice€3.20
Subtotal
€13.00
Discount
−€1.30
VAT 10%
€1.17
Tip
€1.00
Total
€13.87
Cash
€20.00
Change
€6.13

Thank you, come again\nWi-Fi: cornercafe / espresso

ORD-20261028-0042
Kiosk
Date
Newspaper$2.50
Total
$2.50

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/receipt.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/receipt.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/receipt.qwe
view ReceiptDemo() =    <div.demo-row style="align-items: flex-start; gap: 28px">        <Receipt id="receipt-demo-cafe" store="Corner Café" address="12 Bridge Street, Porto" phone="+351 22 000 0000" number="0042"                 date="2026-10-28T09:14:00" cashier="Rui" table="4" currency="€"                 lines={[{ name: "Flat white", qty: 2, unit: "3.50", amount: "7.00" }, { name: "Croissant", amount: "2.80" }, { name: "Orange juice", qty: 1, amount: "3.20" }]}                 subtotal="13.00" discount="−1.30" tax="1.17" tax_label="VAT 10%" tip="1.00" total="13.87"                 paid={[{ method: "Cash", amount: "20.00" }]} change="6.13" footer="Thank you, come again\nWi-Fi: cornercafe / espresso" code="ORD-20261028-0042"/>        <Receipt id="receipt-demo-min" store="Kiosk" date="2026-10-28" lines={[{ name: "Newspaper", amount: "$2.50" }]} total="$2.50"/>    </div>

Source

ui/receipt.qwe
// Receipt: a narrow till receipt: the shop, the date and number, a line each with// quantity and amount, the totals, how it was paid, and a message at the bottom,// in a monospace face on paper with a torn edge. Amounts are text already written// in their currency.////   <Receipt store="Corner Café" address="12 Bridge St" number="0042" date="2026-10-28T09:14:00"//            lines={[{ name: "Flat white", qty: 2, amount: "7.00" }, { name: "Croissant", amount: "2.80" }]}//            subtotal="9.80" tax="0.98" tax_label="VAT 10%" total="10.78" paid={[{ method: "Card", amount: "10.78" }]}//            footer="Thank you, come again"/>//// store, address, phone: whose receipt. number: the receipt's; date: an ISO date or// date-time, shown as "28 Oct 2026, 09:14"; cashier, table: who served, where.// lines: a list of { name, qty?, unit?, amount }. subtotal, discount, tax, tip, total:// the totals (each left out, not shown); tax_label: "Tax" by default. paid: a list of// { method, amount } (cash, card, ...), and change: what was given back.// footer: a line or two at the bottom. code: a code written under it (an order// number), and children for a Barcode or QrCode. currency: a symbol written before// each amount ("" by default, the amounts being text already). id: its id.import ui/theme.qwe use ui_clslet receipt_css = css`.ui-receipt { position: relative; width: 300px; max-width: 100%; margin: 0; padding: 22px 18px 30px; background: var(--ui-surface); color: var(--ui-fg);  font: 12.5px/1.5 var(--ui-mono); box-shadow: var(--ui-shadow); border-radius: 2px 2px 0 0; }.ui-receipt::after { content: ""; position: absolute; left: 0; right: 0; bottom: -8px; height: 8px;  background: linear-gradient(135deg, var(--ui-surface) 50%, transparent 50%) 0 0 / 12px 8px repeat-x,              linear-gradient(45deg, transparent 50%, var(--ui-surface) 50%) 0 0 / 12px 8px repeat-x; }.ui-receipt-head { margin: 0 0 12px; text-align: center; }.ui-receipt-store { font-size: 16px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }.ui-receipt-head p { margin: 0; color: var(--ui-muted); }.ui-receipt-meta { display: grid; grid-template-columns: auto 1fr; gap: 0 10px; margin: 0; padding: 8px 0; border-top: 1px dashed var(--ui-strong); border-bottom: 1px dashed var(--ui-strong); }.ui-receipt-meta dt { color: var(--ui-muted); }.ui-receipt-meta dd { margin: 0; text-align: right; }.ui-receipt-lines { width: 100%; margin: 8px 0; border-collapse: collapse; }.ui-receipt-lines td { padding: 2px 0; vertical-align: top; }.ui-receipt-lines .ui-receipt-amt { text-align: right; white-space: nowrap; padding-left: 10px; }.ui-receipt-qty { color: var(--ui-muted); }.ui-receipt-totals { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; margin: 0; padding: 8px 0; border-top: 1px dashed var(--ui-strong); }.ui-receipt-totals dt { color: var(--ui-muted); }.ui-receipt-totals dd { margin: 0; text-align: right; }.ui-receipt-total dt, .ui-receipt-total dd { font-size: 15px; font-weight: 700; color: var(--ui-fg); padding-top: 4px; }.ui-receipt-paid { border-top: 1px dashed var(--ui-strong); }.ui-receipt-foot { margin: 10px 0 0; padding-top: 10px; border-top: 1px dashed var(--ui-strong); text-align: center; white-space: pre-line; }.ui-receipt-code { display: grid; justify-items: center; gap: 6px; margin-top: 12px; text-align: center; letter-spacing: .08em; }.ui-receipt-code:empty { display: none; }@media print { .ui-receipt { box-shadow: none; background: #fff; color: #000; } }`let receipt_months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]// An ISO date or date-time as "28 Oct 2026" or "28 Oct 2026, 09:14"; other text as it is.fn receipt_date(d) -> str {    let s = str(d)    let p = split(slice(s, 0, 10), "-")    if len(p) != 3 || int(p[1]) == null || int(p[2]) == null { return s }    let day = `${int(p[2])} ${receipt_months[int(p[1]) - 1]} ${p[0]}`    len(s) >= 16 && (s[10] == "T" || s[10] == " ") ? `${day}, ${slice(s, 11, 16)}` : day}view Receipt(store: str, address: str? = null, phone: str? = null, number: str? = null, date = null, cashier: str? = null,             table: str? = null, lines: list = [], subtotal: str? = null, discount: str? = null, tax: str? = null, tax_label: str = "Tax",             tip: str? = null, total: str? = null, paid: list = [], change: str? = null, footer: str? = null, code: str? = null,             currency: str = "", id: str? = null, class: str? = null, children) {    style global receipt_css    // A minus goes before the symbol: "−€1.30".    let amt = (a) => a == null ? "" : starts_with(str(a), "−") || starts_with(str(a), "-") ? `${str(a)[0]}${currency}${slice(str(a), 1, len(str(a)))}` : `${currency}${a}`    <article class={ui_cls(["ui-receipt", class])} id={id} aria-label={`Receipt${number != null ? " " + number : ""} from ${store}`}>        <header.ui-receipt-head>            <div.ui-receipt-store>{store}</div>            {address != null && <p>{address}</p>}            {phone != null && <p>{phone}</p>}        </header>        {(number != null || date != null || cashier != null || table != null) && <dl.ui-receipt-meta>            {date != null && [<dt>Date</dt>, <dd><time datetime={str(date)}>{receipt_date(date)}</time></dd>]}            {number != null && [<dt>Receipt</dt>, <dd>{number}</dd>]}            {cashier != null && [<dt>Served by</dt>, <dd>{cashier}</dd>]}            {table != null && [<dt>Table</dt>, <dd>{table}</dd>]}        </dl>}        <table.ui-receipt-lines>            <tbody>                {for [i, l] in enumerate(lines) => <tr key={i}>                    <td>{l.qty != null && <span.ui-receipt-qty>{l.qty} × </span>}{l.name}{l.unit != null && <span.ui-receipt-qty> @ {amt(l.unit)}</span>}</td>                    <td.ui-receipt-amt>{amt(l.amount)}</td>                </tr>}            </tbody>        </table>        {(subtotal != null || total != null) && <dl.ui-receipt-totals>            {subtotal != null && [<dt>Subtotal</dt>, <dd>{amt(subtotal)}</dd>]}            {discount != null && [<dt>Discount</dt>, <dd>{amt(discount)}</dd>]}            {tax != null && [<dt>{tax_label}</dt>, <dd>{amt(tax)}</dd>]}            {tip != null && [<dt>Tip</dt>, <dd>{amt(tip)}</dd>]}            {total != null && <div.ui-receipt-total style="display: contents"><dt>Total</dt><dd>{amt(total)}</dd></div>}        </dl>}        {len(paid) > 0 && <dl.ui-receipt-totals.ui-receipt-paid>            {for [i, p] in enumerate(paid) => [<dt key={`m${i}`}>{p.method}</dt>, <dd key={`a${i}`}>{amt(p.amount)}</dd>]}            {change != null && [<dt>Change</dt>, <dd>{amt(change)}</dd>]}        </dl>}        {footer != null && <p.ui-receipt-foot>{footer}</p>}        <div.ui-receipt-code>{children}{code != null && <span>{code}</span>}</div>    </article>}
Thread
66Useful? Like it, or add to its thread below.

Thread 0 posts

On Receipt: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.