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</>
      </>
    </>
  </>,
);

Takumi has no orphans or widows control. A heading can land alone at the foot of a page. Wrapping the heading and its first paragraph in a break-inside: avoid box prevents 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 with working links is <a href="#section-id"> plus a matching id. See Links, outline & metadata.

Running bands

A report header usually names the document and the period. A footer usually carries the page counter:

import {  } from "takumi-pdf";

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">
      < ="pageNumber" /> / < ="totalPages" />
    </>
  ),
  : { : 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

Takumi lays out tables as flex rows, and a header row does not repeat across pages on its own. Split the rows into groups and give each group its own header:

import "takumi-pdf";

< ="flex flex-col">
  {(, 24).((, ) => (
    < ={} ="flex break-inside-avoid flex-col">
      < />
      {.(() => (
        < ={.} ="flex break-inside-avoid gap-3 pt-2 text-xs">
          < ="flex-1">{.}</>
          < ="w-[100px] text-right">{.}</>
        </>
      ))}
    </>
  ))}
</>;

break-inside-avoid moves a group that no longer fits to the next page, header and all. Pick a group size that underfills a page, or the group stops fitting anywhere and splits again.

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