Takumi

Links, outline & metadata

Clickable hyperlinks, a table of contents, bookmarks, and document properties.

Anchors with an href become clickable link annotations. They appear on every page that their box touches:

import {  } from "takumi-pdf";

const  = await (
  <>
    Pay online at < ="https://example.com/pay/1042">example.com/pay/1042</>.
  </>,
);

Inline anchors annotate each text run. A link that wraps across lines stays clickable on both lines. Block-level anchors annotate their entire box.

http, https, mailto and tel links open outside the document. An href starting with # points inside it instead, to the element carrying that id:

import {  } from "takumi-pdf";

const  = await (
  <>
    < ="#appendix">Jump to the appendix</>
    < ="appendix">Appendix</>
  </>,
);

Clicking it moves to the page holding that element. A fragment matching no element is dropped, so the reader never gets a link that goes nowhere. Other schemes are dropped too: they have no meaning inside a standalone document.

Table of contents

A node classed targetPageNumber prints the page its link points at. The link is the nearest enclosing element carrying an href, usually the <a> around the entry, so one piece of markup gives an entry that is both clickable and numbered:

import {  } from "takumi-pdf";

const  = await (
  <>
    < ="#results" ="flex items-baseline gap-2">
      <>Results</>
      < ="flex-1 border-b border-dotted border-gray-400" />
      < ="targetPageNumber" ="w-8 shrink-0 text-right" />
    </>
    < ="results" ={{ : "page" }}>
      Results
    </>
  </>,
);

The stretched span draws the dot leader. Counter styles work here too, so targetPageNumber upper-roman prints IV. See Counter styles for the full list.

A fragment naming no element prints nothing. This follows the rule links already use.

Why the fixed width

A page number exists only after pagination. Takumi paginates, fills the numbers in, and lays the document out again. A number that widens its line can move a page break, and the moved break can renumber the entry. The render repeats this up to three times, then keeps what it has.

A number that cannot widen its line settles on the second pass. That is what w-8 shrink-0 text-right does above. Reserve enough width for the highest page number the document reaches.

Headers and footers are laid out per page, outside this loop. A targetPageNumber in a band renders nothing.

Outline

Set outline: true to build PDF bookmarks from h1–h6 headings. Deeper headings nest under the previous shallower heading. This matches an HTML document outline:

import {  } from "takumi-pdf";

const  = await (, { : true });

Clicking a bookmark jumps to the page and position of its heading.

Document metadata

metadata fills the PDF document properties. lang also sets the metadata language:

import {  } from "takumi-pdf";

const  = await (, {
  : "en",
  : {
    : "Annual report 2026",
    : "Consolidated results for fiscal year 2026",
    : ["Acme Inc."],
    : ["annual report", "2026"],
    : "acme-reporting",
  },
});

All fields are optional. Omitting metadata writes no document properties. This keeps output byte-identical across runs for golden testing.

Last updated on

On this page