react-print-pdf
v0.3.0
Published
Browser-side React to vector PDF from the live DOM. Tailwind v4, fonts, pagination, images, and raster fallback.
Maintainers
Readme
react-print-pdf
Browser-side React → vector PDF. Reads computed styles from the live DOM so Tailwind v4, CSS-in-JS, and design tokens work without a second layout system.
react-print-pdf exports an already-rendered React document to PDF in the browser. It walks the mounted DOM, reads layout and computed styles, paginates primitives, and emits a PDF with pdf-lib.
Status
Public release. Current version: 0.2.0 on npm under the latest dist-tag.
- Browser-only for v1
- React-only public integration
- MIT licensed
- Docs site:
docs/
Install
npm install react-print-pdfTry it in your browser without cloning:
https://stackblitz.com/github/hempun10/react-print-pdf/tree/main/examples/starterQuick start
react-print-pdf ships two ways to drive an export. Both call the same pipeline; pick whichever fits your call site.
Hook + Printable (recommended for React apps)
import { Printable, usePDFExport } from "react-print-pdf";
export function InvoiceExport() {
const { ref, exportPDF, isExporting, error } = usePDFExport({
format: "A4",
margin: "20mm",
filename: "invoice.pdf",
title: "Invoice #1042",
});
return (
<>
<button onClick={exportPDF} disabled={isExporting}>
{isExporting ? "Exporting…" : "Export PDF"}
</button>
{error && <p role="alert">{error.message}</p>}
<Printable ref={ref} format="A4" margin="20mm">
<Invoice />
</Printable>
</>
);
}<Printable> sizes itself to the PDF page automatically. usePDFExport owns the ref, loading state, error state, and result handle. By default it calls result.save() for you on success; pass autoSave: false and read lastResult for upload-only flows.
Functional (use it from anywhere)
exportToPDF takes a mounted HTMLElement. In React, render your printable document normally and pass ref.current.
import { useRef } from "react";
import { exportToPDF, PageBreak, registerFont } from "react-print-pdf";
export function InvoiceExport() {
const printRef = useRef<HTMLDivElement>(null);
async function exportInvoice() {
if (!printRef.current) return;
await registerFont({
family: "Inter",
src: "/fonts/Inter-Regular.ttf",
weight: 400,
});
const result = await exportToPDF(printRef.current, {
format: "A4",
margin: "20mm",
filename: "invoice.pdf",
title: "Invoice #1042",
header: ({ pageNumber, totalPages }) => (
<div className="text-xs text-gray-500">
Page {pageNumber} / {totalPages}
</div>
),
onPageRendered: (n, total) => console.log(`rendered ${n}/${total}`),
});
result.save();
}
return (
<>
<button type="button" onClick={exportInvoice}>
Export PDF
</button>
<div ref={printRef} style={{ width: 640 }}>
<Invoice />
<PageBreak />
<Appendix />
</div>
</>
);
}Use the returned handle directly when you do not want an immediate download:
const result = await exportToPDF(printRef.current, options);
result.save();
result.save("copy.pdf");
result.preview();
const bytes = result.bytes();
const blob = result.blob();Why not Puppeteer or @react-pdf/renderer?
Headless Chromium is accurate, but it is server-side infrastructure: a large binary, cold starts, operational work, and privacy questions for user documents.
@react-pdf/renderer is useful when you want a React-like PDF-only layout tree, but it asks you to rebuild your UI with <Document>, <Page>, <View>, and <Text> primitives plus a smaller CSS subset.
react-print-pdf takes a third path: the browser remains the layout engine. Your existing React/Tailwind/CSS-in-JS document renders normally, then the library captures the computed result and emits PDF primitives.
What works in 0.2
- DOM layout capture via
getBoundingClientRect()and computed styles - Text from
Range.getClientRects() - Registered TTF/OTF fonts with browser
FontFaceloading and PDF embedding - CSS colors, including Tailwind v4
oklch()colors through browser resolution - Solid backgrounds and borders, including per-side colors and rounded corners
- True alpha — semi-transparent fills/borders compose against underlying content correctly (ExtGState)
- Soft card backgrounds that continue across page breaks (rounded corners only at the start and end of the run)
- Hyperlinks —
<a href>becomes a clickable PDF Link annotation, including links that wrap onto multiple lines - Explicit page breaks with
<PageBreak /> - Automatic pagination by page content height
- CSS
break-before/break-after(page/always/left/right, plus the legacypage-break-*aliases) break-inside: avoidwith overflow warnings when an item taller than a page is asked to stay together- Headers and footers rendered per page with
{ pageNumber, totalPages } - Repeating bands with
data-print-repeat, typically for table headers - PNG/JPEG
<img>embedding from data URLs, same-origin URLs, blob URLs, and CORS-permitted remote URLs - Raster fallback for gradients, shadows, transforms, filters, SVG, and manual
data-pdf-raster="true"regions - Typed errors —
PdfExportErrorwithcode+cause, plus anisPdfExportError(e)guard - Lifecycle hooks —
onProgress(stage, pct)for the five-stage pipeline,onPageRendered(pageNumber, totalPages)for per-page callbacks - Debug overlay —
debug: truepaints colored outlines + labels on every emitted primitive's box - React DX layer —
usePDFExporthook,<Printable>auto-sized container, and<ExportButton>for the simple case
Current limitations
- Browser-only; no Node/SSR export path yet
- Source DOM should fit the PDF content width (
640pxis a safe A4 +20mmmargin starting point) - Fonts must be TTF or OTF; WOFF/WOFF2 cannot be embedded by
pdf-lib object-fiton images is not honored yet- Text decoration, text shadow, letter spacing, and word spacing are incomplete
- SVG is rasterized, not emitted as PDF vector paths
- Soft card backgrounds with mixed-color borders fall back to the legacy atomic-rect path
See docs/content/docs/limitations.mdx for the full list.
Demo
cd examples/demo
npm install
npm run devOpen the Vite URL and click Export PDF. The demo includes invoices, long tables, cards, text fixtures, raster effects, and image embedding.
Starter
A minimal Vite + React + react-print-pdf app lives at examples/starter. Open it directly in StackBlitz:
https://stackblitz.com/github/hempun10/react-print-pdf/tree/main/examples/starterOr run locally:
cd examples/starter
npm install
npm run devDocs site
Production docs and landing page: https://react-print-pdf-inky.vercel.app
Live component gallery ("workshop"): https://react-print-pdf-workshop.vercel.app
Run the docs locally:
cd docs
npm install
npm run dev # http://localhost:3001The docs app is a separate Next.js + Fumadocs project under docs/. See docs/DEPLOY.md for the Vercel + GitHub Actions wiring.
Development
npm install
npm run lint
npm run typecheck
npm run test
npm run buildVisual/demo coverage lives under examples/demo and Playwright configs.
License
MIT
