Invoice
Sourcecomponents/Invoice.astro
The page
A fixed A4 invoice (210×297mm) — logo, sender & recipient blocks, invoice meta, a title, an intro, a line-item table, the total, closing notes, and a three-column bank footer. Everything sits on one 3-column grid, so the sender block lines up with the bank column below. Sizes are fixed so it renders identically on screen and on paper.
Shown scaled to fit. It prints one A4 page — Cmd/Ctrl-P from the standalone frame gives a clean PDF.
Sender variant
Same document, a different sender — here Dimitri Surber. Only the sender, meta, notes sign-off and the first footer column change; nothing about the layout is bespoke.
Behaviour
- Fixed A4 — a flex column at
min-height: 297mm; a spacer pushes the footer to the foot, so it never overlaps the notes however short the body is. - Print-ready —
@page { size: A4; margin: 0 }; the on-screen page shadow drops away in print. - Data-driven — every block is a prop; a multi-line
descbecomes a bullet list.intro/notesaccept inline HTML for line breaks.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
recipient | Party | — | Left block (client) — { name?, lines: string[] }. Required. |
sender | Party | — | Right block (you) — { name?, lines: string[] }. Required. |
meta | Meta[] | [] | Right, under the sender — { label, value }[] (number, date, due). |
title | string | — | The invoice heading. Required. |
intro | string[] | [] | Greeting + paragraphs before the table (each may contain inline HTML). |
columns | {pos,desc,qty,unit,sum} | German labels | Override the table header labels. |
items | LineItem[] | — | { pos, desc: string[], qty, unit, sum }. A multi-line desc renders as bullets. Required. |
total / totalLabel | string | 'Gesamtsumme' | The total row — the summed amount and its label. |
notes | string[] | [] | Paragraphs after the total (tax note, sign-off). Inline HTML allowed. |
footer | FootCol[] | [] | 2–3 columns of bank / contact details — { lines: string[] }. First line is emphasised. |
logo / logoAlt | string | '/img/logo-dark.svg' | The header logo and its alt text. |
Usage
Render it on its own route (no site chrome), then print to PDF. All money is passed pre-formatted so the component never does maths — your billing layer owns the numbers.
<Invoice
recipient={{ name: 'Bastiaan Feurer & Team', lines: ['Peach Technologies AG', '8834 Schindellegi'] }}
sender={{ name: 'Miriam Beck', lines: ['Ikigai2 GmbH', 'miriam@alchemy.zuerich'] }}
meta={[
{ label: 'Rechnungsnummer', value: '0345' },
{ label: 'Rechnungsdatum', value: '18.06.2026' },
{ label: 'Zahlbar bis', value: '18.07.2026' },
]}
title="Rechnung für Verpackungsdesign & Mockups"
intro={['Lieber Bastiaan & Team', 'Vielen Dank für Euren Auftrag …']}
items={[{ pos: 1, desc: ['Aufbereitung von Druckdaten …', 'Aufbereitung von PSD Mockups'], qty: 9, unit: 'CHF 120.00', sum: 'CHF 1’080.00' }]}
total="CHF 1’080.00"
notes={['Nach Art. 10 Abs. 2 MWSTG. …', 'Liebe Grüsse,<br />Miriam']}
footer={[
{ lines: ['Miriam Beck', '+41 78 871 05 03', 'miriam@alchemy.zuerich'] },
{ lines: ['Ikigai2 GmbH', 'Edelweissstrasse 41', '8048 Zürich'] },
{ lines: ['PostFinance', 'IBAN: CH08 0900 0000 1646 2132 1', 'BIC: POFICHBEXXX'] },
]}
/> Rules
Pass amounts already formatted (CHF, thousands, decimals).
Keep it to one page — it's designed to fill exactly one A4.
Wrap it in the site nav or footer — it's a standalone document.
Swap the fixed pt sizes for the responsive type tokens — they'd shift the print.