Help us improve
Share bugs, ideas, or general feedback.
From react-pdf
Generates PDF documents using React-PDF (@react-pdf/renderer) with TypeScript and JSX for reports, invoices, forms, resumes needing flexbox layouts, SVG graphics, and custom fonts.
npx claudepluginhub trailofbits/skills-curated --plugin react-pdfHow this skill is triggered — by the user, by Claude, or both
Slash command
/react-pdf:react-pdfThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
- Generating PDF documents (reports, invoices, resumes, forms, certificates)
Converts Markdown documents to professionally typeset PDFs using reportlab (primary) or pandoc+XeLaTeX (fallback). Handles CJK/Latin mixed text, code blocks, tables, Obsidian callouts, math formulas, and 14 color themes. Triggered by "markdown to PDF", "md2pdf", "any2pdf", or export requests.
Reads, creates, and reviews PDFs with visual rendering checks via Poppler/pdftoppm to PNGs, using reportlab for generation and pdfplumber/pypdf for extraction where layout matters.
Generates print-ready PDFs from scratch, fills form fields, or reformats documents using token-based design systems for reports, proposals, resumes.
Share bugs, ideas, or general feedback.
pdfplumber or pypdf insteadpypdf or pdftk(async () => { ... })()
pattern.Font.registerHyphenationCallback((word) => [word]); after registering custom fonts.{baseDir}/skills/react-pdf/references/google-fonts.txt - Metadata for ~65 popular Google Fonts with TrueType URLs. Each
line is a font variant in tab-separated format: font name, style, category, weight, url.{baseDir}/skills/react-pdf/references/components.md - Full component API reference and supported CSS properties{baseDir}/skills/react-pdf/assets/example-template.tsx - Minimal working example demonstrating fixed footers, page numbers,
and unbreakable content. Read this before starting to understand the basic patterns. Note: not all
APIs are shown here — always refer to the docs and {baseDir}/skills/react-pdf/references/components.md for the full API.npm install react @react-pdf/renderer
npm install -D tsx @types/react
tsx runs TypeScript + JSX files directly via Node with no config — no tsconfig.json needed. It
uses esbuild under the hood and handles JSX transformation automatically.
For full component props and CSS properties, see references/components.md.
import React from "react";
import { Document, Page, Text, View, StyleSheet, renderToFile } from "@react-pdf/renderer";
const styles = StyleSheet.create({
page: { flexDirection: "column", backgroundColor: "#ffffff", padding: 40 },
title: { fontSize: 24, marginBottom: 20, fontWeight: "bold" },
text: { fontSize: 12, lineHeight: 1.5 },
});
const MyDocument = () => (
<Document>
<Page size="A4" style={styles.page}>
<View style={{ margin: 10, padding: 20 }}>
<Text style={styles.title}>Document Title</Text>
<Text style={styles.text}>Your content here</Text>
</View>
</Page>
</Document>
);
(async () => {
await renderToFile(<MyDocument />, "./output.pdf");
console.log("PDF saved!");
})();
PDF generation scripts use JSX, which Node cannot run directly. Use tsx to execute them:
npx tsx my-document.tsx
npx tsx works without installing tsx globally — it downloads on demand. If tsx is installed as a
dev dependency (npm install -D tsx), it runs instantly without the npx download step.
Always wrap rendering in async IIFE:
// Good
(async () => {
await renderToFile(<MyDocument />, "./output.pdf");
})();
// Bad - top-level await may fail
await renderToFile(<MyDocument />, "./output.pdf");
To visually inspect generated PDFs, convert pages to images. Try pdftoppm first (often
pre-installed), fall back to Python's PyMuPDF if unavailable.
Option 1: pdftoppm (poppler-utils) — preferred, no install needed in many environments:
pdftoppm -png -r 200 document.pdf preview
# → preview-1.png, preview-2.png, ...
Option 2: PyMuPDF (Python) — fallback if pdftoppm is not available:
pip install pymupdf
import fitz
doc = fitz.open("document.pdf")
for i, page in enumerate(doc):
pix = page.get_pixmap(dpi=200)
pix.save(f"page-{i+1}.png")
import { renderToFile, renderToBuffer } from "@react-pdf/renderer";
// To file
(async () => {
await renderToFile(<MyDocument />, "./document.pdf");
})();
// To buffer
(async () => {
const buffer = await renderToBuffer(<MyDocument />);
})();
Three methods: StyleSheet.create(), inline objects, or mixed arrays.
const styles = StyleSheet.create({ container: { padding: 20 } });
<View style={styles.container} />
<View style={{ padding: 20 }} />
<View style={[styles.container, { marginTop: 10 }]} />
pt (default, 72 DPI), in, mm, cm, %, vw, vh
{
// Flexbox
flexDirection: "row", justifyContent: "space-between", alignItems: "center",
flexWrap: "wrap", gap: 10,
// Box model
margin: 10, padding: 20, width: "100%", height: 200,
// Borders
borderWidth: 1, borderColor: "#333", borderRadius: 5, borderStyle: "solid",
// Colors
backgroundColor: "#f0f0f0", color: "#000", opacity: 0.8,
// Typography
fontSize: 12, fontWeight: "bold", fontFamily: "Helvetica", fontStyle: "italic",
lineHeight: 1.5, textAlign: "center", textDecoration: "underline",
textTransform: "uppercase", letterSpacing: 1,
// Position
position: "absolute", top: 0, left: 0, right: 0, bottom: 0, zIndex: 10,
// Transforms
transform: "rotate(45deg)", transformOrigin: "center",
}
Local files are most reliable. Remote URLs may fail due to network/CORS issues.
import { Image } from '@react-pdf/renderer';
<Image src="./images/photo.jpg" style={{ width: 200, height: 150 }} />
<Image src={{ data: buffer, format: 'png' }} />
SVG files cannot be used as Image sources. Read the SVG source and recreate using react-pdf Svg components.
import { Svg, Circle, Rect, Path, Line, G, Defs, LinearGradient, Stop } from "@react-pdf/renderer";
<Svg width="200" height="200" viewBox="0 0 200 200">
<Defs>
<LinearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<Stop offset="0%" stopColor="#3498db" />
<Stop offset="100%" stopColor="#9b59b6" />
</LinearGradient>
</Defs>
<Circle cx="100" cy="100" r="50" fill="url(#grad1)" />
<Rect x="10" y="10" width="50" height="50" fill="#e74c3c" />
<Path d="M10,50 Q50,10 90,50" stroke="#2ecc71" strokeWidth="2" fill="none" />
</Svg>;
Read SVG source from icon libraries and convert to react-pdf Svg components:
npm install lucide-static
import { Svg, Path, Rect } from "@react-pdf/renderer";
// Converted from lucide-static/icons/mail.svg
const MailIcon = ({ size = 12, color = "#888" }) => (
<Svg width={size} height={size} viewBox="0 0 24 24">
<Path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" stroke={color} strokeWidth={2} fill="none" />
<Rect x="2" y="4" width="20" height="16" rx="2" stroke={color} strokeWidth={2} fill="none" />
</Svg>
);
<Link src="https://example.com"><Text>Visit website</Text></Link>
<View id="section-1"><Text>Target</Text></View>
<Link src="#section-1"><Text>Jump to Section 1</Text></Link>
<Text render={({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`} />
<Page size="A4">
<View fixed style={{ position: "absolute", top: 20, left: 30, right: 30 }}>
<Text>Header</Text>
</View>
<View style={{ marginTop: 60, marginBottom: 60 }}>
<Text>Content</Text>
</View>
<Text
fixed
style={{ position: "absolute", bottom: 20, left: 30, right: 30, textAlign: "center" }}
render={({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`}
/>
</Page>
<View break /> // Force page break
<View wrap={false}><Text>Keep together</Text></View> // Prevent breaking inside
<Text orphans={2} widows={2}>Long text...</Text> // Orphan/widow control
<View minPresenceAhead={100}><Text>Content</Text></View> // Min space before break
CRITICAL: All font sources MUST be local file paths. Remote URLs do not work.
import { Font } from "@react-pdf/renderer";
Font.register({
family: "Roboto",
fonts: [
{ src: "./fonts/Roboto-Regular.ttf", fontWeight: "normal" },
{ src: "./fonts/Roboto-Bold.ttf", fontWeight: "bold" },
{ src: "./fonts/Roboto-Italic.ttf", fontStyle: "italic" },
],
});
// Always disable hyphenation when using custom fonts
Font.registerHyphenationCallback((word) => [word]);
Built-in fonts: Courier, Helvetica, Times-Roman (each with Bold, Italic/Oblique variants)
Font weight values: thin (100), ultralight (200), light (300), normal (400), medium (500), semibold (600), bold (700), ultrabold (800), heavy (900)
Use {baseDir}/skills/react-pdf/references/google-fonts.txt to find font URLs, then download locally:
# Find the font URL
grep "^Roboto" {baseDir}/skills/react-pdf/references/google-fonts.txt | grep "700" | grep "normal"
# Download
mkdir -p fonts
curl -sL "<url-from-grep>" -o fonts/Roboto-Bold.ttf
# Verify - must show "TrueType Font data"
file fonts/Roboto-Bold.ttf
If file shows "HTML document" or "ASCII text", the download failed. Try a different URL or search
GitHub for the font's official repo with TTF files.
Emoji won't render in PDFs unless you register an emoji source. Install twemoji-emojis to get
local Twemoji PNG assets — no internet needed at render time.
npm install twemoji-emojis
import { Font } from "@react-pdf/renderer";
Font.registerEmojiSource({
format: "png",
url: "node_modules/twemoji-emojis/vendor/72x72/",
});
Then use emoji directly in Text: <Text>Hello</Text>
// Canvas drawing
<Canvas style={{ width: 200, height: 200 }}
paint={(painter, w, h) => { painter.circle(w/2, h/2, 50).fill("#3498db"); }} />
// Annotation notes
<Note style={{ color: "yellow" }}>Annotation text</Note>
// Hyphenation
Font.registerHyphenationCallback((word) => [word]); // disable
// Debug mode - visualize boundaries
<View debug><Text debug>Debug text</Text></View>
// Document metadata
<Document title="My Doc" author="Author" subject="Report" language="en-US" pdfVersion="1.5" />
StyleSheet.create() — define styles once and reusecache={true} for remote imagesfixed prop for headers/footers on every pagedebug={true} to visualize element boundariesText overflow: <Text style={{ width: 200, maxLines: 3, textOverflow: "ellipsis" }}>...</Text>
Missing fonts: Download locally and register with local file paths. Remote URLs will NOT work.
Unexpected page breaks: Use wrap={false} to keep content together, or <View break /> to
force breaks.