Home/ Cookbook/ Views/ Print-friendly views

Build a print-friendly receipt and report

By the end of this recipe you'll have a record view that prints — or exports to PDF via the browser's print dialog — at the same quality as the on-screen experience, with controlled page breaks, a header on every page, a monochrome palette, and zero leftover chrome.

Views 2 primitives · 1 block · 1 pattern ~30 min React · @corelithzw/react

Overview

A printable view is a second view of the same record. Same data, different layout, same care.

The recipe takes a record (a sale, an invoice, a report) and gives you a second rendering that the browser's print pipeline can turn into paper or PDF. It uses a @media print stylesheet to drop the nav and sidebar, swap to a serif body, force a monochrome palette, and control where the page breaks. A "Print" button calls window.print() — no PDF library required.

Never use the browser default. Always design the print version with the same care as the screen version. Customers see the receipt; treat it as marketing.

Skip this recipe if your receipts and reports already live in a server-rendered PDF pipeline. Browser-printed PDFs are perfect for SMB volumes; if you're issuing 50k tax invoices a night, use a real PDF renderer.

What you'll build

Three print artefacts from the same React tree, each tuned to its medium.

MUKAMBA GROUP

Receipt #00184 · 03 Jun 2026

2× Coke 500ml$3.00
1× Bread$1.20
3× Eggs$2.10
Total$6.30
01 Receipt — 80mm thermal width

SALES REPORT

May 2026 · Mukamba Group

Gross$48,212
Refunds-$612
Discounts-$1,940
Net$45,660
Page 1 of 4
02 Report — A4 with running header

INVOICE

INV-2026-0019 · Due 17 Jun

Bill to: Zimworx Pvt Ltd

Consulting · 12 hr$960
VAT 14.5%$139.20
Due$1,099.20
03 Invoice — itemised, VAT-aware

Required pieces

A small surface — most of the work is the @media print stylesheet, not new components.

@corelithzw/react exports used here: Button, Card, Stack. Plus a regular CSS file with @media print rules.

Step-by-step build

01

Mark which DOM is printable and which isn't

The screen view contains chrome — sidebar, toolbar, action menus — that shouldn't print. Tag every printable region with data-print="region" and everything else with data-print="hide". That single attribute is what the print stylesheet will key off; no className wrangling.

Code-only step

data-print="region" → CSS gives full page width

No JS branch for print — same React tree, two stylesheets.

Switch to the Code tab to see the snippet.

Step 1 · Tag the DOM@corelithzw/react
02

Write the print stylesheet

Reset margins on @page, drop the nav, force monochrome, and choose a paper-friendly type. The order matters: @page first, then root resets, then component-specific overrides. Keep the file under 80 lines — anything more and you're redesigning, not adapting.

MUKAMBA GROUP
Receipt #00184 · 03 Jun 2026

2× Coke 500ml$3.00
1× Bread$1.20
3× Eggs$2.10
Total$6.30
Step 2 · print.css
03

Add a running header and footer for multi-page reports

For a one-page receipt you're done; reports need a brand mark and page number on every page. position: fixed on a sub-tree inside @media print behaves as a running header. Combine with page-break-before: always on section titles to control where pages break.

MUKAMBA GROUPSALES REPORT · MAY 2026
May 2026 sales summary

Gross sales for May rose 4.2% against April, driven by the Bulawayo outlet's weekend trading days. The pages that follow break out branches, categories, and refunds.

Page 1 of 4
Step 3 · Running header
04

Trigger print from a button and reset the trigger

The print button calls window.print() directly. For the thermal-receipt mode you add a class to <html> before printing, then remove it in the afterprint event. Don't try to detect "did the user actually print?" — you can't.

Print receipt
Save as PDF
Step 4 · Trigger + cleanup

Final composition

The whole ReceiptView + print.css. Drop both into your app; the rest of your shell stays as-is.

MUKAMBA GROUP
Receipt
#00184
Issued
03 Jun 2026 14:22
ItemAmount
2× Coke 500ml$3.00
1× Bread$1.20
3× Eggs$2.10
Total$6.30

Thank you. Return within 7 days with this receipt.

ReceiptView.tsx + print.css@corelithzw/react

Variations

Three forks. Each is a small diff from the final composition.

PDF export via window.print()

The "Save as PDF" option in every browser's print dialog is already a PDF exporter. Label the button accordingly; the file the user gets is genuinely a PDF.

// Just rename the button
<Button onClick={print} variant="primary">
  Save as PDF
</Button>
{/* The browser dialog already
    offers PDF as a destination. */}

Printable invoice with VAT

An invoice differs from a receipt only by metadata and signature block. Reuse the same view, hide payment lines, and add a "Bill to" panel.

// Reuse ReceiptView, swap header
<header className="invoice-head">
  <h1>INVOICE</h1>
  <BillTo party={sale.buyer} />
</header>
// add to print.css
.invoice-head h1 { font: 700 22pt/1 monospace; }

Batch print of records

Print N receipts back-to-back. Each receipt gets a hard page break before; the print dialog still opens once.

// BatchPrint.tsx
{sales.map((s) => (
  <section className="report-page" key={s.id}>
    <ReceiptView sale={s} />
  </section>
))}
// print.css already breaks per .report-page

Accessibility

Print accessibility is the same problem as screen accessibility — semantic markup that survives whatever rendering pipeline the browser picks.

  • The receipt is a real <article> with a real <table>. Screen readers and PDF accessibility checkers can both read the structure. No "div soup" line items.
  • Numeric columns use <th scope="col">. The totals row uses <th scope="row">Total</th> so the row label is announced with the amount.
  • Print button is a real <button>. Keyboard activates it; it lives in the document order, not behind a hover menu.
  • Link URLs print. a[href]::after { content: " (" attr(href) ")" } means a printed copy is still useful when the link can't be clicked.
  • Colour is never load-bearing. The monochrome stylesheet means anyone printing on a B/W laser still sees the same hierarchy.
  • Focus is preserved across afterprint. The button that triggered the dialog remains focused — Tab order picks up where it left off.