Takumi Sticker

Takumi

Render OG images and paged PDFs from JSX, HTML, and CSS. No headless browser.

Takumi renders images in Node.js, Cloudflare Workers, browsers, and Rust applications. The PDF package runs in Node.js, Bun, and Cloudflare Workers.

npm version npm version crates.io npm downloads license

Documentation · Playground · Showcase

Quick Start

bun i takumi-js    # PNG, JPEG, WebP, SVG, animations
bun i takumi-pdf   # paged PDF

Image

import { render } from "takumi-js";
import { writeFile } from "node:fs/promises";

const image = await render(
  <div tw="w-full h-full flex items-center justify-center bg-gradient-to-b from-blue-100 to-red-50">
    <h1 tw="text-6xl font-bold">Hello from Takumi</h1>
  </div>,
  { width: 1200, height: 630 },
);

await writeFile("./output.png", image);

PDF

import { render } from "takumi-pdf";
import { PageNumber, TotalPages } from "takumi-pdf/primitives";
import { writeFile } from "node:fs/promises";

const pdf = await render(<Invoice data={data} />, {
  size: "a4",
  footer: (
    <div tw="flex w-full justify-center text-[10px] text-gray-500">
      Page <PageNumber /> of <TotalPages />
    </div>
  ),
});

await writeFile("invoice.pdf", pdf);

Features

Takumi is a Rust rendering engine for markup and CSS. It handles layout, text shaping, compositing, and encoding without launching a browser. One component tree renders as an image, an animation, or a paged PDF.

Images

  • text-fit grows or shrinks a headline to fill its line box, no measuring loop.
  • text-wrap: balance evens multi-line headlines. pretty spares the orphan word.
  • Animations sample the tree across time. @keyframes and animate-spin become WebP, APNG, GIF, or video frames.
  • Stylesheets apply as written, with complex selectors, var(), calc(), and media queries.
  • CSS Grid, block, inline, and float handle layout.
  • lang picks each language's own Han glyphs for the same code points.
  • Text on a path follows offset-path. background-clip: text and conic gradients paint it.
  • Masks, clip-path, backdrop-filter, and blend modes composite the way browsers do.
  • SVG filters run through filter: url(...), feTurbulence and feDisplacementMap included.
  • corner-shape swaps round corners for squircle, bevel, scoop, notch, or superellipse(n).
  • Tailwind v4 utilities apply directly, arbitrary values included.

PDF

  • Page breaks honor break-before: page, break-after: page, and break-inside: avoid.
  • Widows and orphans default to 2, keeping lone lines away from page breaks.
  • Headers and footers repeat on every page. <PageNumber /> and <TotalPages /> count in CSS counter styles, trad-chinese-informal included.
  • Tables share column widths across pages and repeat their <thead> on every page.
  • Text stays selectable and searchable. Fonts embed as subsets.
  • Links and metadata carry into the output. outline: true builds bookmarks from headings.
  • Attachments embed files, including Factur-X e-invoice XML.
  • Tagged PDF is on by default. PDF/A-2, A-3, A-4, and PDF/UA-1 pass veraPDF.
  • Arabic, bidi text, CJK, and emoji shape correctly.
  • 1.5 MB of gzip wasm fits the Cloudflare Workers free plan.

Runtimes

  • Node.js and Bun load a native binding, prebuilt for macOS, Linux (glibc and musl), and Windows on x64 and ARM64.
  • Cloudflare Workers and browsers load the WebAssembly build.
  • Rust applications embed the takumi crate.

Coming From Something Else

You are usingWhat changes
satoriReplace satori() with renderSvg(), or call render() for encoded image bytes. Compare the renderers.
next/ogSwap the ImageResponse import. Existing Satori-compatible templates keep their explicit Flexbox styles. Read the migration guide.
Puppeteer or Playwright for PDFsReplace page.pdf() with render(). You must preload remote assets, and Takumi supports less CSS than Chrome. Read the migration guide.
@react-pdf/rendererReplace Document, View, and Text with HTML elements and CSS. Browser viewers and some text controls have no equivalent. Read the migration guide.
pdfkitUse JSX or HTML instead of positioning each line. Keep pdfkit when you need low-level drawing control.

More Output

Fonts

Only a last-resort Latin font ships built in. Load the rest through fonts: a URL, raw bytes, or googleFonts. A weight range or an axes entry loads the variable font, so font-variation-settings drives its axes:

import { render } from "takumi-js";
import { googleFonts } from "takumi-js/helpers";

const image = await render(
  <div
    tw="w-full h-full flex items-center justify-center"
    style={{
      fontSize: 72,
      fontFamily: "Fraunces",
      fontVariationSettings: "'opsz' 72, 'wght' 700",
    }}
  >
    Hello from Takumi
  </div>,
  {
    width: 1200,
    height: 630,
    fonts: googleFonts([{ name: "Fraunces", weight: "100..900", axes: { opsz: "9..144" } }]),
  },
);

Rendering many images? Register the fonts once on a Renderer and reuse it. See Typography & Fonts.

API route (next/og-compatible)

import { ImageResponse } from "takumi-js/response";

export function GET() {
  return new ImageResponse(
    <div tw="w-full h-full flex items-center justify-center bg-gradient-to-b from-blue-100 to-red-50">
      <h1 tw="text-6xl font-bold">Hello from Takumi</h1>
    </div>,
    { width: 1200, height: 630 },
  );
}

Animated WebP

import { renderAnimation } from "takumi-js";
import { writeFile } from "node:fs/promises";

const animation = await renderAnimation({
  width: 400,
  height: 400,
  fps: 30,
  format: "webp",
  scenes: [
    {
      durationMs: 1000,
      node: (
        <div tw="w-full h-full flex items-center justify-center">
          <div tw="w-32 h-32 bg-blue-500 animate-spin rounded-lg" />
        </div>
      ),
    },
  ],
});

await writeFile("./output.webp", animation);

Vector SVG

import { renderSvg } from "takumi-js";
import { writeFile } from "node:fs/promises";

const svg = await renderSvg(
  <div tw="w-full h-full flex items-center justify-center bg-gradient-to-b from-blue-100 to-red-50">
    <h1 tw="text-6xl font-bold">Hello from Takumi</h1>
  </div>,
  { width: 1200, height: 630 },
);

await writeFile("./output.svg", svg);

Rust

cargo add takumi

Start from the Rust example.

Comparison

Images

Featurenext/og (Satori)Takumi
RuntimeNode / EdgeNode, Edge, CF Workers, Browser, Rust crate
Template inputJSX / ReactJSX, HTML strings, JSON node trees from any language
LayoutFlexboxFlexbox, CSS Grid, block, inline, float
SelectorsLimitedComplex selectors, :is(), :where(), ::before, ::after
backdrop-filter, blend modes
Animated outputWebP / APNG / GIF / video frames
Vector SVG output✅ NativePlus raster and animated output
Headless browser
ImageResponse API✅ NativeCompatible

Compare rendering output across providers at image-bench.kane.tw.

PDF

The benchmark uses an 80-line invoice with two pages and a page-number footer. Warm figures are the median of 20 renders. The environment was an Apple M1 Pro with macOS 15.7.4, Bun 1.3.14, and Chrome 151. The bench source reproduces the table.

takumi-pdf 0.4@react-pdf/renderer 4.5.1Puppeteer + Chrome
Cold start to first PDF176 ms495 ms0.7 to 2.8 s
Warm render (median)26 ms236 ms198 ms
Output size19 KB (15 KB with tagged: false)16 KB52 KB
Deploy needs1.5 MB gzip wasmpure JSChrome install (hundreds of MB)
Template languageJSX, HTML, node trees with CSS and Tailwindits own primitives (<View>, <Text>, StyleSheet)HTML with full CSS
Selectable text, subset fontsyesyesyes
Arabic and bidi textyesshaping only, manual directionyes
Runs on edge runtimesyes (Cloudflare Workers)no (Node)no

Chrome has the most complete CSS support of the three. Takumi's PDF output does not support filter: blur(), drop-shadow(), or backdrop-filter. Use Puppeteer when you need to reproduce a complex web page pixel for pixel. See the PDF comparison for install sizes and more caveats.

Who's Using Takumi

  • Dcard renders post share images
  • TanStack renders OG images for its docs
  • Fumadocs generates its docs OG images
  • Nuxt OG Image ships Takumi as a built-in renderer
  • Luma renders event share images
  • shiki-image turns syntax-highlighted code into images

More projects in the showcase. Takumi is part of the Vercel OSS Program.

Core Architecture

Takumi converts any template into a node tree with three node kinds: container, image, and text. That tree runs through:

  1. Layout via taffy: Flexbox, Grid, block, float, calc(), absolute positioning, z-index
  2. Text shaping via parley and skrifa: WOFF/WOFF2 fonts, emoji, RTL, multi-span inline blocks
  3. Compositing: stacking contexts, blend modes, filters, transforms, SVG via resvg
  4. Output: PNG, JPEG, WebP, ICO for statics; GIF, APNG, WebP for animations; paged PDF; raw RGBA frames for video pipelines
  • Any template system that serializes to HTML or JSON can feed the node tree: React, Svelte, Vue, plain strings, or your own serializer in any language.
  • A time axis threads through the pipeline. A PNG is the tree at t=0. A GIF samples the same tree across t. CSS @keyframes, the animation shorthand, and Tailwind animation utilities resolve at render time.
  • The same layout drives two vector backends: renderSvg() for Satori-style SVG, takumi-pdf for paged PDF.
flowchart LR
    A[Templates] --> N[Node Tree] --> P[Rendering Pipeline]
    C[Stylesheets] --> P
    R[Resources] --> P
    D(Time Axis) -.-> P

    P --> F[(Raw Pixels)]
    P --> S[Vector SVG]
    P --> V[Paged PDF]

    F --> G[PNG / JPEG / WebP / ICO]
    F --> H[GIF / APNG]
    F --> I[Video frames]

Showcase

Takumi OG image (source)Package OG card (source)
Takumi OG ImagePackage OG Image
Prisma-style API card (source)X-style social post (source)
Prisma OG ImageX-style Post Image
Keyframe Animation (source)shiki-image
Keyframe AnimationShiki Image Example

See more examples for invoices, e-invoices, Next.js, Cloudflare Workers, TanStack Start, Svelte, Rust, and ffmpeg keyframe animation.

Contributing

Read CONTRIBUTING.md for local setup and the fixture workflow.

We welcome bug reports, feature requests, doc improvements, and new example integrations.

License

MIT or Apache-2.0


Vercel OSS Program