Takumi

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.

Reserve space for an invoice footer
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.

Keep a line item together
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 requiresThe option
A PDF/A-3 containerpdfa: "3b"
The XML attached by nameattachments
An fx: XMP blockmetadata.xmp
A modification date per filemodificationDate
Embedded subset fontsalways 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:

Attach invoice XML
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:

Validate PDF/A and PDF/UA
verapdf --flavour 3b --format text invoice.pdf
verapdf --flavour ua1 --format text invoice.pdf

Mustang checks the Factur-X half, both the XML and the packaging:

Validate Factur-X with Mustang
java -jar Mustang-CLI.jar --action validate --source invoice.pdf

Mustang 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