Invoice — line items + totals
Tabular invoice with auto-summed totals and tax. Expects
data.invoice,
data.from,
data.to,
data.items[].
#set page(margin: 2cm)
#set text(font: "Inter", size: 10pt)
#text(size: 22pt, weight: "bold")[
Invoice #data.invoice.number
]
#grid(
columns: (1fr, 1fr),
gutter: 2em,
[*From* \\ #data.from.name \\ #data.from.address],
[*Bill to* \\ #data.to.name \\ #data.to.address],
)
#v(1em)
#table(
columns: (1fr, auto, auto, auto),
align: (left, right, right, right),
[*Description*], [*Qty*], [*Unit*], [*Total*],
..data.items.map(it => (
[#it.description],
[#it.quantity],
[#data.currency#it.unit_price],
[#data.currency#calc.round(it.quantity * it.unit_price, digits: 2)],
)).flatten()
)
#let subtotal = data.items.fold(0, (s, it) => s + it.quantity * it.unit_price)
#let tax = subtotal * data.invoice.tax_rate
#let total = subtotal + tax
#align(right)[
*Subtotal:* #data.currency#subtotal \\
*Tax (#int(data.invoice.tax_rate * 100)%):* #data.currency#tax \\
*Total:* #data.currency#total
] {
"invoice": {
"number": "INV-2026-0042",
"tax_rate": 0.20
},
"currency": "€",
"from": {
"name": "PaperJet",
"address": "France"
},
"to": {
"name": "Acme Corp",
"address": "123 Main St, NYC"
},
"items": [
{
"description": "Monthly subscription",
"quantity": 1,
"unit_price": 29
},
{
"description": "Overage 100 PDFs",
"quantity": 100,
"unit_price": 0.005
}
]
}