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

<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. Pass href instead to have it render its own link. Underneath it is the targetPageNumber class hook, which HTML input uses directly.

import { ,  } from "takumi-pdf";

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

The stretched span draws the dot leader. Counter styles work here too, so format="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