Render JSX to paged, selectable-text PDF with takumi-pdf.
takumi-pdf renders the same JSX, Tailwind classes, and node trees as image output, but writes a paged vector PDF: selectable and searchable text, embedded subset fonts, page breaks, and repeating headers and footers. It ships as a WebAssembly module and runs on Node.js, Bun, and Cloudflare Workers without Chromium.
npm i takumi-pdfRender a document
import { render } from "takumi-pdf";
import { googleFonts } from "@takumi-rs/helpers";
import { writeFile } from "node:fs/promises";
const pdf = await render(<Invoice data={data} />, {
// A4 portrait with a 48px margin is the default
size: "a4",
fonts: await googleFonts(["Inter"]),
footer: (
<div tw="flex w-full justify-center text-[10px] text-gray-500">
Page <span className="pageNumber" /> of <span className="totalPages" />
</div>
),
});
await writeFile("invoice.pdf", pdf);render() accepts JSX, HTML strings converted with @takumi-rs/helpers, or JSON node trees, and returns Uint8Array PDF bytes. Content lays out at the page's content width and flows onto as many pages as it needs.
Page setup
const pdf = await render(report, {
size: "letter", // "a4", "letter", or { width, height } in CSS px at 96 dpi
landscape: true,
margin: { top: 48, right: 32, bottom: 48, left: 32 },
});| Option | Type | Default | Description |
|---|---|---|---|
size | "a4", "letter", or { width, height } | "a4" | Page size in CSS px at 96 dpi. Presets ignore case. |
landscape | boolean | false | Swaps page width and height, including explicit sizes. |
margin | number or { top?, right?, bottom?, left? } | 48 | A number applies to all sides. Missing object sides are 0. |
@page CSS rules are not supported; these options are the page geometry.
Reuse a renderer
render() keeps one shared renderer alive. When an application manages several font sets, construct PdfRenderer directly:
import { PdfRenderer } from "takumi-pdf";
const renderer = new PdfRenderer();
await renderer.registerFont("https://example.com/Inter-Regular.woff2");
const pdf = await renderer.render(doc);Registered fonts deduplicate across calls, so rendering many documents pays the font cost once.
Last updated on