The code of the preview above.
demos/invoice.qwe
view InvoiceDemo() = <div.demo-stack> <Invoice id="invoice-demo-due" number="INV-2026-0042" date="2026-10-01" due="2026-10-31" status="due" logo="https://dummyimage.com/180x44/5b4bdb/ffffff.png&text=ENGINES" from={{ name: "Analytical Engines Ltd", lines: ["1 Difference Street", "London N1 9AA"], email: "billing@example.com", tax_id: "VAT GB 123 4567 89" }} to={{ name: "Babbage & Co", lines: ["22 Dorset Street", "London W1U 6QW"], email: "charles@example.com" }} lines={[ { description: "Design", detail: "October, 12 days at the day rate", qty: 12, unit: "€90.00", amount: "€1,080.00" }, { description: "Hosting", detail: "Two services, one region", qty: 1, unit: "€120.00", amount: "€120.00" }, { description: "Domain renewal", qty: 2, unit: "€14.50", amount: "€29.00" }, ]} subtotal="€1,229.00" discount="−€29.00" tax="€240.00" tax_label="VAT 20%" total="€1,440.00" paid="€500.00" balance="€940.00" notes="Thank you for your business.\nBank: Example Bank, IBAN GB00 EXMP 0000 0000 0000 00" terms="Payable within 30 days. Late payments carry 2% a month."/> <Invoice id="invoice-demo-paid" number="Q-118" title="Quote" date="2026-09-12" status="paid" to={{ name: "Ada Lovelace" }} lines={[{ description: "A short consult", amount: "$250.00" }]} total="$250.00"/> </div>
ui/invoice.qwe
import ui/theme.qwe use ui_clslet invoice_css = css`.ui-invoice { display: grid; gap: 28px; min-width: 0; padding: 36px 40px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.5 var(--ui-font); }.ui-invoice-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 16px 32px; }.ui-invoice-logo { max-height: 44px; max-width: 180px; object-fit: contain; margin-bottom: 10px; }.ui-invoice-title { display: flex; align-items: center; gap: 10px; margin: 0; font-size: 26px; font-weight: 700; letter-spacing: -.01em; line-height: 1.2; }.ui-invoice-status { padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 600; letter-spacing: .03em; text-transform: uppercase; background: var(--ui-subtle); color: var(--ui-muted); }.ui-invoice-paid { background: var(--ui-success-soft); color: var(--ui-success); }.ui-invoice-due { background: var(--ui-info-soft); color: var(--ui-info); }.ui-invoice-overdue { background: var(--ui-danger-soft); color: var(--ui-danger); }.ui-invoice-meta { display: grid; grid-template-columns: auto auto; gap: 2px 14px; margin: 0; align-content: start; text-align: right; }.ui-invoice-meta dt { color: var(--ui-muted); }.ui-invoice-meta dd { margin: 0; font-weight: 500; font-variant-numeric: tabular-nums; }.ui-invoice-parties { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; }.ui-invoice-party { font-style: normal; }.ui-invoice-party h4 { margin: 0 0 4px; color: var(--ui-muted); font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }.ui-invoice-party-name { font-weight: 600; }.ui-invoice-party p { margin: 0; }.ui-invoice-party .ui-invoice-dim { color: var(--ui-muted); }.ui-invoice-scroll { overflow-x: auto; }.ui-invoice table { width: 100%; border-collapse: collapse; }.ui-invoice th { padding: 8px 10px; border-bottom: 2px solid var(--ui-fg); color: var(--ui-muted); font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; text-align: left; }.ui-invoice td { padding: 10px; border-bottom: 1px solid var(--ui-border); vertical-align: top; }.ui-invoice .ui-invoice-num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }.ui-invoice-desc { font-weight: 500; }.ui-invoice-detail { display: block; color: var(--ui-muted); font-size: 13px; }.ui-invoice-totals { display: grid; grid-template-columns: auto minmax(120px, auto); gap: 6px 24px; justify-content: end; margin: 0; }.ui-invoice-totals dt { color: var(--ui-muted); }.ui-invoice-totals dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }.ui-invoice-total dt, .ui-invoice-total dd { padding-top: 8px; border-top: 2px solid var(--ui-fg); font-size: 16px; font-weight: 700; color: var(--ui-fg); }.ui-invoice-discount dd { color: var(--ui-success); }.ui-invoice-balance dt, .ui-invoice-balance dd { font-weight: 600; color: var(--ui-fg); }.ui-invoice-notes { display: grid; gap: 8px; padding-top: 16px; border-top: 1px solid var(--ui-border); }.ui-invoice-notes h4 { margin: 0 0 2px; font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ui-muted); }.ui-invoice-notes p { margin: 0; white-space: pre-line; }.ui-invoice-terms { color: var(--ui-muted); font-size: 13px; }@media (max-width: 560px) { .ui-invoice { padding: 22px 18px; } .ui-invoice-meta { text-align: left; } }@media print { .ui-invoice { padding: 0; border: 0; border-radius: 0; background: #fff; color: #000; } .ui-invoice-scroll { overflow: visible; } }`let invoice_months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]fn invoice_date(d) -> str { let p = split(slice(str(d), 0, 10), "-") len(p) == 3 && int(p[1]) != null && int(p[2]) != null ? `${invoice_months[int(p[1]) - 1]} ${int(p[2])}, ${p[0]}` : str(d)}fn invoice_party(heading: str, p: map) = <address.ui-invoice-party> <h4>{heading}</h4> <p.ui-invoice-party-name>{p.name}</p> {for l in p.lines ?? [] => <p key={l}>{l}</p>} {p.email != null && <p.ui-invoice-dim>{p.email}</p>} {p.phone != null && <p.ui-invoice-dim>{p.phone}</p>} {p.tax_id != null && <p.ui-invoice-dim>{p.tax_id}</p>} </address>view Invoice(number: str, title: str = "Invoice", date = null, due = null, status: str? = null, from: map? = null, to: map? = null, logo: str? = null, lines: list = [], subtotal: str? = null, discount: str? = null, tax: str? = null, tax_label: str = "Tax", total: str? = null, paid: str? = null, balance: str? = null, notes: str? = null, terms: str? = null, id: str? = null, class: str? = null) { style global invoice_css let has_qty = any(lines, (l) => l.qty != null) let has_unit = any(lines, (l) => l.unit != null) <article class={ui_cls(["ui-invoice", class])} id={id} aria-label={`${title} ${number}`}> <header.ui-invoice-head> <div> {logo != null && <img.ui-invoice-logo src={logo} alt=""/>} <h2.ui-invoice-title>{title} {status != null && <span class={`ui-invoice-status ui-invoice-${status}`}>{status}</span>} </h2> </div> <dl.ui-invoice-meta> <dt>Number</dt><dd>{number}</dd> {date != null && [<dt>Date</dt>, <dd><time datetime={str(date)}>{invoice_date(date)}</time></dd>]} {due != null && [<dt>Due</dt>, <dd><time datetime={str(due)}>{invoice_date(due)}</time></dd>]} </dl> </header> {(from != null || to != null) && <div.ui-invoice-parties> {from != null && invoice_party("From", from)} {to != null && invoice_party("Bill to", to)} </div>} <div.ui-invoice-scroll> <table> <thead><tr> <th scope="col">Description</th> {has_qty && <th.ui-invoice-num scope="col">Qty</th>} {has_unit && <th.ui-invoice-num scope="col">Unit price</th>} <th.ui-invoice-num scope="col">Amount</th> </tr></thead> <tbody> {for [i, l] in enumerate(lines) => <tr key={i}> <td><span.ui-invoice-desc>{l.description}</span>{l.detail != null && <span.ui-invoice-detail>{l.detail}</span>}</td> {has_qty && <td.ui-invoice-num>{l.qty}</td>} {has_unit && <td.ui-invoice-num>{l.unit}</td>} <td.ui-invoice-num>{l.amount}</td> </tr>} </tbody> </table> </div> {(subtotal != null || total != null) && <dl.ui-invoice-totals> {subtotal != null && [<dt>Subtotal</dt>, <dd>{subtotal}</dd>]} {discount != null && <div.ui-invoice-discount style="display: contents"><dt>Discount</dt><dd>{discount}</dd></div>} {tax != null && [<dt>{tax_label}</dt>, <dd>{tax}</dd>]} {total != null && <div.ui-invoice-total style="display: contents"><dt>Total</dt><dd>{total}</dd></div>} {paid != null && [<dt>Paid</dt>, <dd>{paid}</dd>]} {balance != null && <div.ui-invoice-balance style="display: contents"><dt>Balance due</dt><dd>{balance}</dd></div>} </dl>} {(notes != null || terms != null) && <footer.ui-invoice-notes> {notes != null && <div><h4>Notes</h4><p>{notes}</p></div>} {terms != null && <p.ui-invoice-terms>{terms}</p>} </footer>} </article>}