Takumi

Pagination

Control PDF page breaks, keep related content together, and set paragraph widows and orphans with CSS.

Content flows across pages at the width available inside the margins. Use CSS break properties to start chapters, keep related content together, and control how paragraphs split. Percentage heights do not resolve in this mode because the content has no fixed height. Viewport units do: vh is the page area height, as in print media.

Break properties

Break properties
import {  } from "takumi-pdf";

const  = await (
  <>
    < ={{ : "page" }}>Chapter two</>
    < ={{ : "avoid" }}>Keep this together.</>
  </>,
);
PropertyEffect
break-before: pageStarts the element on a new page.
break-after: pageStarts the following content on a new page.
break-inside: avoidKeeps the element on one page when it fits.
box-decoration-break: cloneRepeats borders and backgrounds on every page fragment.

break-inside: avoid applies only when the box fits on a page. An oversized box can still split. Text lines, images, and transformed subtrees are kept together where possible.

Full-page boxes

A 100vh box fills one page inside the margins, and the next box starts on the following page. Use it for a cover or any page laid out by hand:

Full-page cover
import {  } from "takumi-pdf";

const  = await (
  <>
    < ={{ : "100vh", : "flex", : "flex-end" }}>
      <>Annual report</>
    </>
    <>The report starts on page two.</>
  </>,
  { : 0 },
);

vw, vmin, and vmax resolve against the same page area. In the header and footer bands they resolve against the full page.

Named pages

Give an element page: <name> and style that page with @page <name>. The element starts on a new page, its pages take the named rule's size and margins, and the content after it returns to the unnamed page. Adjacent siblings that name the same page share it. A full-bleed cover is a named page with no margin:

Full-bleed cover page
import {  } from "takumi-pdf";

const  = await (
  <>
    < ={{ : "cover", : "100vh", : "#1e293b" }} />
    <>The report starts on page two, inside the document margins.</>
  </>,
  {
    : "@page cover { margin: 0 }",
    : <>Page footer</>,
  },
);

When the document opens with a named page, viewport units resolve against that page's area, so 100vh above fills the whole sheet.

The header and footer bands draw on every page, including named ones, the way Chromium prints its header and footer over a page with no margin.

This is an approximation of CSS named pages. The element lays out inside a box as wide as its page's content area, so its own width and margins still apply, but its ancestors keep the unnamed page's width and their backgrounds do not widen to the named page. An element inside a named page cannot name another page.

Widows and orphans

A cut through a paragraph keeps at least orphans lines at the bottom of the page and widows lines at the top of the next. Both default to 2, like Chromium. Set them to 1 to allow lone lines:

Widows and orphans
import {  } from "takumi-pdf";

const  = await (
  < ={{ : 3, : 3 }}>
    A long report body wraps into many lines across the page boundary.
  </>,
);

Both properties inherit. If a paragraph is too short to satisfy both values, orphans takes priority. A paragraph moves to the next page when the current page cannot hold the required lines. A requirement larger than a full page is relaxed.

Split decorations

A box that crosses a page break slices its border and background by default. Set box-decoration-break: clone to give each fragment complete decorations:

Split decorations
import {  } from "takumi-pdf";

const  = await (
  <
    ={{
      : "1px solid #d1d5db",
      : 8,
      : "clone",
    }}
  >
    Long content that continues on the next page.
  </>,
);

Repeated table headers

Use <thead> for column headings that repeat on continuation pages. The header stays together, including its spacing before the first body row.

Repeated table headers
import {  } from "takumi-pdf";

const  = await (
  <>
    <>
      <>
        <>Name</>
        <>Qty</>
      </>
    </>
    <>{}</>
  </>,
);

A header taller than a quarter of the page does not repeat, matching Chromium. A header cell whose rowspan reaches into the body suppresses repetition for that table. <tfoot> renders once, after the body.

Page ranges

pageRanges keeps only the listed pages, like a print dialog. Each entry is a 1-based page number or an inclusive span:

Page ranges
import {  } from "takumi-pdf";

const  = await (<>{}</>, {
  : [1, { : 4, : 8 }, { : 12 }],
});

An unset from starts at the first page. An unset to runs to the last. Ranges that keep no page reject the render.

Layout and page counters still run over the whole document. A kept page shows the numbers it would in full output, so page 4 of 12 still reads "Page 4 of 12". Links and outline entries pointing at a dropped page are dropped with it.

Watermarks

position: fixed paints a box on every page. The box lays out against the page area. It stays outside the content column.

Watermarks
import {  } from "takumi-pdf";

const  = await (
  <>
    <
      ={{
        : "fixed",
        : 0,
        : "flex",
        : "center",
        : "center",
      }}
    >
      < ={{ : 96, : "rgba(0,0,0,0.08)" }}>DRAFT</>
    </>
    <>Long content that continues on the next page.</>
  </>,
);

Place the fixed box outside transformed or filtered ancestors. Those ancestors contain the box, so it paginates with them instead of repeating independently.

Use a negative z-index to place the watermark behind content. Set the page color through backgroundColor if the root background would otherwise cover the watermark.

Last updated on

On this page