Invoices
Render a paginated PDF invoice with page numbers and totals, and attach Factur-X or ZUGFeRD XML when the format requires it.
An invoice needs line items that flow across pages, totals that stay in one piece, and a footer with a page counter. Some formats also need embedded XML. Takumi renders the invoice as a paged document, and for those formats you attach the XML and set the PDF/A and XMP options the format requires.
A plain invoice
Here, invoice is your invoice data and InvoiceDocument is your JSX template. Line items flow across pages, and the footer carries the page counter. measure() returns the footer's height, so you can reserve enough bottom margin. Headers & footers covers the footer in full.
import { } from "@takumi-rs/helpers";
import { , } from "takumi-pdf";
import { , } from "takumi-pdf/primitives";
const = (
< ="flex w-full justify-between px-12 text-[10px] text-[#6b7280]">
<>Invoice {.}</>
< ="flex">
Page < /> of < />
</>
</>
);
const = await (["Inter"]);
const { } = await (, { : "a4", });
const = await (< ={} />, {
: "a4",
: { : 48, : + 32, : 48, : 48 },
,
,
});For complete templates, see the invoice and receipt example.
Use break-inside: avoid, or break-inside-avoid in Tailwind, to keep a line item on one page. Pagination lists every break property.
import "takumi-pdf";
< ="flex break-inside-avoid gap-3 pt-3 text-xs">
< ="flex-1">{.}</>
< ="w-[100px] text-right">{.}</>
</>;Apply the same style to the totals block.
Electronic invoices
Factur-X and ZUGFeRD combine a readable PDF invoice with structured XML. The table maps what your profile requires to the option that provides it:
| The profile requires | The option |
|---|---|
| A PDF/A-3 container | pdfa: "3b" |
| The XML attached by name | attachments |
An fx: XMP block | metadata.xmp |
| A modification date per file | modificationDate |
| Embedded subset fonts | always on |
Each attachment's modificationDate falls back to metadata.creationDate, so one document date covers both. See Attachments for every attachment field. This example also validates against PDF/UA-1:
import { } from "takumi-pdf";
const = await (, {
: "en",
: "3b",
: "ua1",
: {
: "Invoice INV-2026-0042",
: "2026-08-06",
: [],
},
: [
{
: "factur-x.xml",
: ,
: "text/xml",
: "Factur-X 1.0 MINIMUM invoice data",
: "data",
},
],
});The renderer checks PDF conformance while writing. It does not check the invoice XML, tax rules, or whether a receiving system accepts the file.
Takumi does not build the invoice XML. Use a library for your profile, or write the elements the profile lists. The e-invoice example writes a MINIMUM profile by hand, along with the facturXmp schema used above.
Validating the result
Two validators cover the two halves, and both need Java. veraPDF checks the container:
verapdf --flavour 3b --format text invoice.pdf
verapdf --flavour ua1 --format text invoice.pdfMustang checks the Factur-X half, both the XML and the packaging:
java -jar Mustang-CLI.jar --action validate --source invoice.pdfMustang finds the profile through the fx: XMP block. Takumi writes each value together with the schema description PDF/A requires, so the two cannot disagree. It does not check the values, so a wrong profile name shows up in Mustang, not at render time.
Accessible invoices
For PDF/UA-1 output, set tagged: "ua1", lang, and metadata.title. Then review the reading order and table structure yourself. See PDF/A & PDF/UA.
Reproducible bytes
The same content, resources, renderer version, and metadata.creationDate produce the same bytes. A new date changes the file even when the invoice looks the same.
Last updated on