Reports & statements
Generate PDF reports and statements with chapters, bookmarks, tables of contents, and repeating headers.
Use page breaks for chapters, repeating headers for context, and bookmarks or a table of contents for navigation.
Chapters
break-before: page starts a section on a fresh page. break-inside: avoid keeps a figure with its caption:
import { } from "takumi-pdf";
const = await (
<>
< ={{ : "page" }}>
<>Results</>
< ={{ : "avoid" }}>
< ={} ="Revenue by quarter" ={{ : 560, : 360 }} />
<>Revenue by quarter</>
</>
</>
</>,
);widows and orphans keep a paragraph from splitting into single lines, but they do not keep a heading with the text under it. To do that, wrap the heading and its first paragraph in a break-inside: avoid box.
See Pagination for every break property.
Bookmarks
outline: true turns h1–h6 into PDF bookmarks, nested by heading depth. Readers jump between chapters from the viewer's sidebar:
import { } from "takumi-pdf";
const = await (, {
: true,
: "en",
: { : "Annual report 2026", : ["Acme Inc."] },
});For a table of contents, link each chapter by its id. Here chapters is your list of { id, title } entries, and <TargetPageNumber /> fills in the page each link points to:
import { } from "takumi-pdf/primitives";
< ="flex flex-col gap-1 text-sm">
{.(() => (
< ={.} ={`#${.}`} ="flex items-baseline gap-2">
<>{.}</>
< ="flex-1 border-b border-dotted border-gray-300" />
< ="w-8 shrink-0 text-right" />
</>
))}
</>;Each entry stays clickable, so the contents page works on screen as well as in print. Give the number a fixed width, or a two-digit page can rewrap the entry and renumber it. See Table of contents.
Running bands
A report header usually names the document and the period, and the footer carries the page counter:
import { } from "takumi-pdf";
import { , } from "takumi-pdf/primitives";
const = await (, {
: (
< ="flex w-full justify-between px-12 text-[10px] text-gray-500">
<>Annual report 2026</>
<>Acme Inc.</>
</>
),
: (
< ="flex w-full justify-center text-[10px] text-gray-500">
< /> / < />
</>
),
: { : 64, : 64, : 48, : 48 },
});Bands draw in the margin, so a numeric margin has to fit them. Leave margin unset to fit them automatically, or see Sizing the margin.
Long tables
Use <table> markup. Rows break between pages, and columns keep the same positions on every page. A <thead> that qualifies repeats at the top of each continuation page, and a <tfoot> below the last row of each page the table breaks across:
import "takumi-pdf";
< ="w-full text-xs">
<>
<>
< ="text-left">Item</>
< ="text-right">Amount</>
</>
</>
<>
{.(() => (
< ={.}>
<>{.}</>
< ="text-right">{.}</>
</>
))}
</>
</>;Tagged output maps the markup to Table, TR, TH, and TD structure elements, so screen readers navigate by row and column. See Tables for layout coverage and PDF/A and PDF/UA for the structure tree.
Batches
A statement run renders the same template many times. Create one PdfRenderer and reuse it, so each font loads once for the whole batch. Here accounts holds your records, Statement is your template, and save(id, pdf) stores each result:
import { } from "takumi-pdf";
import { } from "@takumi-rs/helpers";
const = new ();
const = await (["Inter"]);
for (const of ) {
const = await .(< ={} />, { });
await (., );
}Accessibility
Set tagged: "ua1" to validate against PDF/UA-1. Supply lang and metadata.title, then review heading order, tables, and chart descriptions yourself. See PDF/A and PDF/UA.
Last updated on