Documentation

Navigation

Three tools, for three different jobs. Link makes something clickable, either to the web or to another place in the same document. Anchor marks that place. Bookmark fills the outline tree your PDF viewer shows in its sidebar.

Wrap any element and its whole box becomes the hit area - an image, a box, a row.

Link({ href: "https://jasy.dev" }, Box({ bg: "#f3dc29", padding: 12 }, [Text("Read the docs")]));

A link draws nothing of its own, so style the child if you want it to look like one.

For a link on part of a line, put href on a span instead. Only that run becomes clickable, and the text keeps flowing around it:

Paragraph([
  span("Built with "),
  span("jasy", { href: "https://jasy.dev", color: "#1450aa", bold: true }),
  span(", and the rest of this sentence is not a link."),
]);

A span that wraps onto a second line gets a clickable rectangle per line, so the whole run stays clickable. A plain string works too: Text("jasy.dev", { href: "https://jasy.dev" }) links the entire text.

Jumps inside the document

Mark a target with Anchor, then point a link at it with to instead of href. This is how you build a table of contents that actually works.

import { Document, Page, Column, Row, Text, Link, Anchor } from "@jasy/pdf";

const doc = Document([
  Page([
    Column([
      Text("Contents", { size: 22, bold: true }),
      Link({ to: "install" }, Row({ justify: "between" }, [Text("1. Installation"), Text("jump")])),
    ]),
  ]),
  Page([Anchor({ name: "install" }, Text("1. Installation", { size: 22, bold: true }))]),
]);

to also works on a span, for a "back to the top" in the middle of a sentence. A Link takes exactly one of href or to; giving it both, or neither, throws.

The anchor may live on a page that has not been rendered yet - jasy resolves it by name at the end, so forward references are not a problem you have to think about.

Careful with the word: Anchor here is a jump target. That is unrelated to the h / v anchoring of a Positioned child, which is about placement.

Bookmarks

Bookmark adds an entry to the outline tree - the panel your viewer opens next to the page. level nests it, so chapters and their sections form a collapsible tree.

Bookmark({ title: "2. Installation", level: 1 }, Text("2. Installation", { size: 22, bold: true }));
Bookmark({ title: "2.1 Requirements", level: 2 }, Text("2.1 Requirements", { size: 16, bold: true }));

A level: 2 bookmark hangs under the nearest preceding level: 1. Bookmarks are layout-transparent: the child renders exactly as it would on its own.

One heading, both jobs

A chapter heading usually wants to be a sidebar entry and a jump target. Nest them, and it is both:

Bookmark(
  { title: "2. Installation", level: 1 },
  Anchor({ name: "install" }, Text("2. Installation", { size: 22, bold: true })),
);

See the Handbook in the showroom for all of it in one document: a clickable contents page, a bookmark tree, and links back to the top.

jasypdf

Declarative PDFs in pure TypeScript. ZUGFeRD & XRechnung compliant, with no headless browser and no Java.

Resources

© 2026 Florian Heuberger · MIT License

Built with Nuxt · self-hosted fonts · no trackers