React PDF Reference
Generate downloadable PDF documents from React component definitions with @react-pdf/renderer.
Imports and concepts
import { Download } from "lucide-react";
const pdfStyles = StyleSheet.create({
page: { padding: 40 },
title: { fontSize: 24, marginBottom: 20, fontWeight: "bold" },
body: { fontSize: 12, lineHeight: 1.5 },
});
function ReportPdf({ title }: { title: string }) {
return (
<Document title={title}>
<Page size="A4" style={pdfStyles.page}>
<Text style={pdfStyles.title}>{title}</Text>
<Text style={pdfStyles.body}>This PDF was generated from app data.</Text>
</Page>
</Document>
);
}
export default function App() {
const { download } = useDownload();
const [generating, setGenerating] = React.useState(false);
const handleDownload = async () => {
setGenerating(true);
try {
const blob = await pdf(<ReportPdf title="Monthly Report" />).toBlob();
download(blob, "report.pdf");
} finally {
setGenerating(false);
}
};
return (
<button onClick={handleDownload} disabled={generating}>
{generating ? <span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" /> : <Download className="h-4 w-4" />}
Download PDF
</button>
);
}
Components and layout
Document is the root wrapper and can hold metadata like title, author, subject, and creator. Page defines size, orientation, wrapping, and page styles. View is a flexbox container, Text renders all visible text, Image embeds URLs or data URIs, and Link creates PDF hyperlinks.
<Document title="Invoice" author="Jane Doe">
<Page size="A4" orientation="portrait" style={pdfStyles.page}>
<View style={ { flexDirection: "row", gap: 10 } }>
<Text style={ { flex: 1 } }>Left column</Text>
<Text style={ { flex: 1 } }>Right column</Text>
</View>
<Image src={logoUrl} style={ { width: 100, height: 50 } } />
</Page>
</Document>
All visible strings must be inside Text. Raw text outside Text will error.
Styles and fonts
Use StyleSheet.create() instead of Tailwind classes. React PDF supports a subset of CSS with flexbox layout and point-based measurements.
const pdfStyles = StyleSheet.create({
page: { padding: 40, backgroundColor: "#ffffff" },
header: {
fontSize: 20,
fontWeight: "bold",
marginBottom: 20,
borderBottomWidth: 1,
borderBottomColor: "#e5e7eb",
paddingBottom: 10,
},
row: { flexDirection: "row", paddingVertical: 8 },
cell: { flex: 1, fontSize: 10 },
});
Register custom fonts from app asset URLs when needed.
Font.register({
family: "Roboto",
fonts: [
{ src: "https://...blob-url.../Roboto-Regular.ttf" },
{ src: "https://...blob-url.../Roboto-Bold.ttf", fontWeight: "bold" },
],
});
Multi-page documents
Content wraps to additional pages by default. Use wrap={false} for rows that should stay together, break to force a new page before a section, and fixed for repeated headers or footers.
<Page size="A4" style={ { padding: 40 } }>
<Text fixed style={ { fontSize: 9, color: "#9ca3af", marginBottom: 20 } }>
Company Report - Confidential
</Text>
{items.map((item) => (
<View key={item.id} wrap={false} style={ { marginBottom: 12 } }>
<Text style={ { fontWeight: "bold" } }>{item.title}</Text>
<Text style={ { fontSize: 10 } }>{item.description}</Text>
</View>
))}
</Page>
Best practices
Use pdf().toBlob() with useDownload, keep PDF components separate from DOM UI, put every visible string in Text, name the style object pdfStyles, show a loading state during generation, use fixed for repeated headers and footers, use wrap={false} for indivisible rows, and use break for intentional page boundaries.