import { pdf, Document, Page } from '@imprint-pdf/react';
const response = await pdf(
<Document>
<Page size="A4" className="p-12 font-sans bg-white">
<h1 className="text-3xl font-bold tracking-tight">Hello, PDF</h1>
<p className="mt-4 text-base leading-relaxed text-pretty">
Real Tailwind. Real React. Real typography. No browser.
</p>
</Page>
</Document>,
);That's it. pdf() returns a Response you can hand to any web framework. Pass
{ as: 'bytes' } for a Uint8Array, { as: 'stream' } for a ReadableStream.
Making a PDF in a JavaScript app usually means one of two bad deals: boot a 200 MB headless Chromium, or learn a bespoke layout DSL and nudge coordinates by hand.
imprint does neither. You write React and Tailwind - the same code you already write - and get a PDF back. It runs wherever your app runs: Node, Bun, the browser, and edge runtimes like Vercel Edge and Cloudflare Workers.
The trick is that it uses the real tools, not lookalikes: the actual Tailwind compiler, professional text shaping and line-breaking, and a real CSS layout engine with Flexbox and Grid. All Apache-2.0, all in one install. (For the curious, that's Tailwind Oxide, HarfBuzz, Knuth–Plass, and Taffy.)
- Real Tailwind, not a subset. Your actual
p-4 grid grid-cols-12 gap-6classes run through the real compiler - plugins, arbitrary values, OKLCH colors, and@themetokens all work. Tailwind v3 and v4, auto-detected. - Real React. JSX, components, hooks, and refs, through a custom reconciler.
HTML tags (
<div>,<h1>,<table>) map to tagged PDF nodes. React 18 and 19. - Real typography. Proper shaping for Arabic, Indic, Thai, and CJK, plus kerning, ligatures, and variable fonts - and paragraph and page breaking that respects widows, orphans, and footnotes.
- One function, everywhere.
pdf(<Doc />)returns aResponse;{ as: 'bytes' }gives aUint8Array,{ as: 'stream' }aReadableStream. Same call on Node, Bun, the browser, and edge. - Edge-ready. Sub-100 ms cold starts on Cloudflare Workers and Vercel Edge - there is no browser to boot.
- CSS Grid and Flexbox. One real layout pass, not a flexbox-only approximation.
- Charts as vectors. Recharts, Visx, ECharts, and Observable Plot SVG flow through crisp at any zoom - not a rasterized screenshot.
- Real form fields.
<TextField>,<Checkbox>,<RadioGroup>, and<Signature>become actual PDF AcroForm fields. - Print-grade output, opt-in. PDF/X-4 + CMYK + ICC profiles, PDF/UA-1 tagged PDF, PKCS#7 signing, and factur-X / ZUGFeRD.
Full documentation lives in docs/. For LLMs / AI agents, see
llms.txt (index) and llms-full.txt (single-file
context).
- Overview - what imprint-pdf does and where it fits
- Quick start - render your first PDF in five minutes
- Philosophy - why no Chromium, why real Tailwind
- Concepts - documents, pages, layout, the writer
- Guides - components, Tailwind, fonts, charts, forms, accessibility
- Frameworks - Next.js, Vite, Bun, Cloudflare
- Cookbook - invoices, contracts, reports
- Reference - every component, every CLI flag
pnpm add @imprint-pdf/react @imprint-pdf/core tailwindcss
pnpm add -D @imprint-pdf/cli
npx imprint initimprint init detects your framework (Next.js, Vite, generic Node), wires the
right plugin into your config, scaffolds an example template + route, and prints
the first command to run.
The same pdf() call works in every runtime. Pick yours.
Next.js / Hono / Bun route
// returns a Response with PDF headers
import { pdf } from '@imprint-pdf/next';
export const GET = () => pdf(<Invoice data={data} />);Cloudflare Worker (edge)
// same pdf(), edge-friendly build
import { pdf } from '@imprint-pdf/react';
import wasm from '@imprint-pdf/react/imprint.wasm';
export default {
fetch: () => pdf(<Invoice data={…} />, { wasm }),
};Write to disk (Node / Bun)
// { as: 'bytes' } gives back a Uint8Array
import { pdf } from '@imprint-pdf/react';
import { writeFileSync } from 'node:fs';
const bytes = await pdf(<Invoice data={data} />, { as: 'bytes' });
writeFileSync('./invoice.pdf', bytes);Source code → React reconciler → PdfNode IR
│
▼
Tailwind resolver
(Oxide v4 or PostCSS v3)
│
▼
Layout pass (Taffy WASM)
+ inline layout (Parley-lite)
+ Knuth–Plass paragraph breaker
+ Plass page breaker
│
▼
Text & vector pipeline
(HarfBuzz, ICU4X, fontkit, resvg)
│
▼
PDF writer (pdf-lib v0 → imprint-pdf v1)
│
▼
Response | Uint8Array | ReadableStream
For the full design, see ARCHITECTURE.md.
| Capability | @react-pdf |
Satori | pdfme | Forme | Chromium SaaS | Typst | imprint-pdf |
|---|---|---|---|---|---|---|---|
| Real Tailwind classes | ❌ (DSL) | subset | ❌ | ✅ | ✅ | ❌ | ✅ |
| React / JSX components | ✅ | ✅ | part. | ✅ | ❌ | ❌ | ✅ |
| Edge runtimes (CF Workers) | part. | ✅ | part. | ✅ | ❌ | ✅ | ✅ |
| CSS Grid | ❌ | ❌ | ❌ | ❓ | ✅ | ✅ | ✅ (Taffy) |
| HarfBuzz-grade shaping | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Knuth–Plass justification | ❌ | ❌ | ❌ | ❌ | Prince ≈ | ✅ | ✅ |
| Variable fonts | part. | ❌ | ❌ | ❓ | ✅ | ✅ | ✅ |
| Arabic / Indic / Thai / CJK | weak | weak | weak | part. | ✅ | ✅ | ✅ |
| Vector charts | ❌ | ❌ | ❌ | ❓ | ✅ | ✅ | ✅ |
| AcroForms in JSX | ❌ | n/a | tmpl. | ✅ | ❌ | ❌ | ✅ |
| PDF/X-4 + CMYK | ❌ | ❌ | ❌ | part. | DocRaptor | part. | ✅ |
| PDF/UA-1 tagged PDF | ❌ | ❌ | ❌ | ✅ | DocRaptor | part. | ✅ |
| Sub-100 ms edge cold start | n/a | ✅ | part. | ✅ | ❌ | ✅ | ✅ |
| Same code: client + edge + server | part. | ✅ | ✅ | ✅ | n/a | ✅ | ✅ |
Measured with @imprint-pdf/bench on an Apple M-series laptop
(Node 20, 30 runs, 5 warmup), rendering the same 1-page invoice through every
JSX-driven library.
| Library | mean | p95 | p99 | Output |
|---|---|---|---|---|
@imprint-pdf/react |
4.3 ms | 5.2 ms | 5.8 ms | 6.2 KB |
@react-pdf/renderer |
13.7 ms | 15.6 ms | 16.8 ms | 3.3 KB |
| Puppeteer (warm, browser reused) | 50.6 ms | 51.1 ms | 51.1 ms | 46.9 KB |
| Puppeteer (cold, launch per render) | 473 ms | 494 ms | 494 ms | 46.9 KB |
3.15× faster than @react-pdf/renderer on the only fair head-to-head comparison
(both consume JSX, both run a layout pass). ~12× faster than warm Chromium,
~110× faster than cold Chromium - the cold number is what serverless functions
actually pay, plus a 200 MB binary download.
pnpm bench # runs competitors + complexity + pipeline + features
pnpm bench:chromium # opt-in, downloads Chromium on first run
pnpm bench:all # everything, including chromiumImperative libraries (pdfkit, pdf-lib, jsPDF) and template-based ones
(pdfme) are intentionally excluded - they're a different paradigm and the
comparison would flatter imprint-pdf on ergonomics while making it look slow on
µs-per-glyph.
examples/next-app- Next.js App Router + RSC + edge routeexamples/vite-react- Vite + React SPA downloadexamples/cloudflare-worker- Worker that streams a PDF in <100 ms coldexamples/bun-server- Bun + Hono PDF endpointexamples/react18-tailwind3-nextjs- Next 14 + React 18 + Tailwind 3 smoke
pnpm --filter @imprint-pdf/example-next-app dev1.0.0-alpha. Architecture is settled (ARCHITECTURE.md);
the API is close. Pin exact versions.
| Phase | Versions | What it means |
|---|---|---|
| Alpha | 1.0.0-alpha.x |
Anything can change in any release. |
| Beta | 1.0.0-beta.x |
Public API frozen modulo bugs. Soak before GA. |
| 1.0 | 1.0.0 |
First stable release. Semver from here on. |
| Post-1.0 | 1.x.y+ |
Backwards-compatible features and fixes. |
See STABILITY.md for the full contract.
imprint-pdf is Apache-2.0 and self-funded. If your company ships PDFs through it
(or would like to), sponsorship unlocks the next milestones on the
roadmap - including the v1.x custom PDF writer that drops the
pdf-lib runtime dependency for a smaller bundle and edge-friendlier output.
GitHub Sponsors → @tamimbinhakim
Apache-2.0 - every package, including the compliance and print add-ons
(PDF/X-4 + CMYK + ICC, PDF/UA-1 tagged PDF, PKCS#7 signing, factur-X / ZUGFeRD).
No commercial seats, no time-bombed source. See LICENSE.
pnpm install
pnpm dev # turbo run dev across all packages
pnpm test # vitest across the monorepo
pnpm typecheck # composite tsc
pnpm lint # biome checkSee CONTRIBUTING.md and
RELEASING.md.
© Tamim Bin Hakim and contributors.