DocumentsStable

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 desc becomes a bullet list. intro/notes accept inline HTML for line breaks.

Props

PropTypeDefaultNotes
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

Do

Pass amounts already formatted (CHF, thousands, decimals).

Keep it to one page — it's designed to fill exactly one A4.

Don't

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.