Takumi

Reports & statements

Long documents with chapters, bookmarks, and running bands.

Reports and account statements are long. They break into chapters, repeat a band on every page, and get navigated rather than read straight through.

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" }}>
        < ="chart.svg" ="Revenue by quarter" />
        <>Revenue by quarter</>
      </>
    </>
  </>,
);

widows and orphans keep a paragraph from splitting into lone lines, but they do not tie a heading to the text under it. Wrap the heading and its first paragraph in a break-inside: avoid box for that.

See Pagination for the full set of break properties.

Bookmarks

outline: true turns h1h6 into PDF bookmarks. A reader opens the sidebar and jumps between chapters. Nesting follows heading depth:

import {  } from "takumi-pdf";

const  = await (, {
  : true,
  : "en",
  : { : "Annual report 2026", : ["Acme Inc."] },
});

A table of contents is the same chapters as links, with the page each one starts on. <TargetPageNumber /> fills the page in:

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. A footer usually 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 the margin has to be tall enough. Headers & footers shows how to derive the margin from the band.

Long tables

Use <table> markup. Rows split across pages, and column positions stay identical on every page. A <thead> that qualifies repeats at the top of each continuation page:

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 for the structure tree.

Batches

Statement runs render the same template thousands of times. Construct one PdfRenderer and keep it. Fonts register once and are reused for every document:

import {  } from "takumi-pdf";
import {  } from "@takumi-rs/helpers";

const  = new ();
const  = await (["Inter"]);

for (const  of ) {
  const  = await .(< ={} />, {  });
  await (., );
}

Set tagged: false when nobody reads these with assistive technology. It drops the structure tree and the file gets smaller.

Accessibility

Public bodies often have to publish accessible documents. tagged: "ua1" validates the structure tree against PDF/UA-1 during the render. It builds on the heading outline above. See PDF/A for the rest of its inputs.

Last updated on

On this page