Reports & statements
Build a PDF report or statement run with chapters, bookmarks, a table of contents, repeating headers, and long tables.
A report needs chapters that start on fresh pages, bookmarks or a table of contents for navigation, headers that give context on every page, and tables that continue across pages. A statement run adds one more need, which is many documents from one template. Each section below covers one of these, and links to the guide that owns the feature.
Chapters
break-before: page starts a section on a fresh page, and break-inside: avoid keeps a figure with its caption. Pagination lists every break property.
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.
Bookmarks
outline: true turns h1–h6 into PDF bookmarks, nested by heading depth. Readers jump between chapters from the viewer's sidebar. Links, outline & metadata covers the outline and metadata options.
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 & 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 & PDF/UA.
Last updated on