---
title: "How to convert HTML to PDF in React with jsPDF"
canonical_url: "https://www.nutrient.io/blog/how-to-convert-html-to-pdf-using-react/"
md_url: "https://www.nutrient.io/blog/how-to-convert-html-to-pdf-using-react.md"
last_updated: "2026-06-19T09:21:00.105Z"
description: "Learn how to convert HTML to PDF in React with jsPDF — setup, useRef, custom fonts, multipage output, encryption, html2canvas capture, and a library comparison."
---

**TL;DR**

Convert HTML to PDF in React without a server. This guide covers importing [jsPDF](https://parall.ax/products/jspdf), referencing JSX with `useRef`, and exporting a PDF — plus custom page sizes, multiple pages, and custom fonts. jsPDF works for basic needs but lacks external CSS support and requires a font converter for non-standard fonts.

## What can jsPDF convert to PDF?

To convert HTML to PDF in React with jsPDF, reference the rendered element with `useRef`, pass it to jsPDF’s `html()` method, and then call `save()` to download the file. The [live demo examples](https://parallax.github.io/jsPDF/) show that jsPDF can render images, shapes, tables, styled text, and multilingual content as PDF. It also supports multipage output with page breaks, password protection, and annotations. Page orientation — landscape or portrait — is configurable.

## Setting up jsPDF for HTML to PDF in React

Before you start, you’ll need Node.js 18 or later and basic familiarity with React.

This tutorial uses [Vite](https://vitejs.dev/guide/#scaffolding-your-first-vite-project) to scaffold the React project:

```bash

npm create vite@latest convert-html-to-pdf -- --template react

```

Change to the newly created directory and install the `jspdf` package through `npm` or `yarn`:

```bash

cd convert-html-to-pdf

```

```bash

npm install jspdf

# or

yarn add jspdf

# or

pnpm install jspdf

```

## Step-by-step: Convert HTML to PDF in React

1. Now, open the `App.jsx` file and replace the default code with the following:

   ```js

   import jsPDF from 'jspdf';

   // Initialize a new instance of `jsPDF`:
   const doc = new jsPDF();
   ```

   The constructor accepts configuration options for paper size, orientation, and units. Defaults are A4, portrait, and millimeters.

   Pass an object to the constructor to override the defaults:

   ```js

   const doc = new jsPDF({
   	orientation: 'landscape', // or "portrait"
   	unit: 'px', // "mm", "cm", "in" also supported
   	format: [612, 792], // US Letter in px [width, height]
   });
   ```

   You’ll find all the available options in the jsPDF [documentation](https://parallax.github.io/jsPDF/docs/jsPDF.html).

2. The [`html()`](https://parallax.github.io/jsPDF/docs/module-html.html#~html) method converts an HTML element to PDF. It takes an HTML element and an options object that includes a `callback` property. Use the [`useRef()`](https://react.dev/reference/react/useRef) hook to get a reference to the HTML element.

   The `doc.save()` method takes the name of the PDF file as an argument and downloads the file to the user’s computer:

   ```js

   doc.html(htmlElement, {
   	async callback(doc) {
   		await doc.save('document');
   	},
   });
   ```

## Adding the markup

jsPDF renders the report template below. A button click triggers the PDF generation.

Create a new file called `ReportTemplate.jsx` and add the following code:

```js

import SalesChart from './sales-chart.png';

const styles = { img: { width: '100%' }, h1: { marginBottom: '1rem' } };

export default function ReportTemplate() {
	return (
		<div
			style={{
				padding: '40px',
				background: 'white',
				color: 'black',
			}}
		>
			<h1 style={styles.h1}>Sales Report 2025</h1>
			<img src={SalesChart} style={styles.img} alt="Sales chart" />
		</div>
	);
}

```

Add a sample image to the `src` folder and name it `sales-chart.png`.

## Generating the PDF

Next, wire up the button click handler in `App.jsx`. Import the `useRef` hook and the `ReportTemplate` component:

```js

import { useRef } from 'react';
import jsPDF from 'jspdf';
import ReportTemplate from './ReportTemplate';

function App() {
	const reportTemplateRef = useRef(null);

	const handleGeneratePdf = () => {
		const doc = new jsPDF({
			format: 'a4',
			unit: 'px',
		});

		// Adding the fonts.
		doc.setFont('Inter-Regular', 'normal');

		doc.html(reportTemplateRef.current, {
			async callback(doc) {
				await doc.save('document');
			},
		});
	};

	return (
		<div>
			<button className="button" onClick={handleGeneratePdf}>
				Generate PDF
			</button>
			<div ref={reportTemplateRef}>
				<ReportTemplate />
			</div>
		</div>
	);
}

export default App;

```

Run `npm run dev` and click the button to generate a PDF.

## Adding custom fonts

jsPDF supports 14 standard PDF fonts. To add custom fonts, you need to use a font converter. [Navigate to this font converter](https://parallax.github.io/jsPDF/fontconverter/fontconverter.html) and upload `.ttf` files.

Select **Choose Files** to choose the fonts you want to add. Then click **Create** to convert them. You don’t need to modify the **fontName**, **fontStyle**, or **Module format** fields.![jsPDF font converter tool showing file upload and Create button for converting TTF to JavaScript](@/assets/images/blog/2022/how-to-convert-html-to-pdf-using-react/font-converter.png)

The font converter generates a JavaScript file with the provided `.ttf` file as a Base64-encoded string and some code for jsPDF. Add the generated JavaScript file to your project.

To activate the custom font, use the [`setFont()`](https://parallax.github.io/jsPDF/docs/jsPDF.html#setFont) method:

```js

doc.setFont('Inter-Regular', 'normal');

```

The font name is visible in the converter before you generate the JavaScript file. Repeat this process for each additional font.

<!---
You can find the demo project on CodeSandbox. Feel free to fork it and play around with it. The fonts and images used in the demo project are in the `public` folder.

[![Create PDFs with React to PDF](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/html-to-pdf-react-lf7qtm)
--->

## Known limitations of jsPDF

| Limitation                                                    | Workaround                                                                                                                           |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **External CSS is not automatically loaded**                  | Apply styles via inline CSS or imported stylesheets at build time, or ensure styles are rendered in the DOM before calling `html()`. |
| **Custom fonts require manual conversion**                    | Use jsPDF’s font converter tool to generate font files compatible with jsPDF and embed them manually.                                |
| **Large or complex HTML content may overflow or get cut off** | Manually implement pagination logic, or split content into smaller sections before rendering to PDF.                                 |
| **Sparse official documentation**                             | Rely on community examples and GitHub issues, or inspect jsPDF source code for advanced use cases.                                   |

For features like text search, annotations, redaction, digital signatures, and pixel-perfect rendering, consider using a dedicated solution, such as the [Nutrient React viewer](https://www.nutrient.io/sdk/web/getting-started/react-vite.md).

## Advanced HTML-to-PDF techniques in React

Beyond basic export, jsPDF supports plugins for tables, encryption, and styled captures.

### Capture styled components with html2canvas

When your component relies on external CSS (flex/grid, classes, web fonts), jsPDF’s `html()` method ignores those styles. A common workaround is to rasterize the node with [html2canvas](https://html2canvas.hertzen.com/) and embed the result as an image. Extract this into a helper so any component can call it.

The original `html2canvas` is in maintenance mode and doesn’t support modern CSS color functions like `oklch` (used by Tailwind v4 and many current design systems). For new projects, use the maintained fork [`html2canvas-pro`](https://github.com/yorickshan/html2canvas-pro) — a drop-in replacement with the same API.

Install html2canvas:

```bash

npm i html2canvas

```

Create a helper file (`src/utils/pdfHelpers.js`):

```jsx

import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';

/**
 * Generates an A4 PDF snapshot of a DOM node with full CSS support.
 * @param {HTMLElement} node – the element to capture
 * @param {string} filename – optional, defaults to "styled.pdf"
 */
export async function downloadStyledPdf(node, filename = 'styled.pdf') {
	const canvas = await html2canvas(node, { useCORS: true, scale: 2 }); // higher scale = crisper output
	const imgData = canvas.toDataURL('image/png');
	const pdf = new jsPDF({ unit: 'px', format: 'a4' });

	const pageWidth = pdf.internal.pageSize.getWidth();
	const pageHeight = (canvas.height * pageWidth) / canvas.width;

	pdf.addImage(imgData, 'PNG', 0, 0, pageWidth, pageHeight);
	pdf.save(filename); // → defaults to styled.pdf
}

```

The helper rasterizes the element, converts it to PNG, and places it on an A4 page. The output defaults to `styled.pdf`.

html2canvas is pixel-based, so vector text becomes an image. For crisp text, use server-side rendering or a true PDF layout engine.

Use the helper in your component:

```js

import { useRef } from 'react';
import { downloadStyledPdf } from './utils/pdfHelpers';
import ReportTemplate from './ReportTemplate';

export default function App() {
	const reportRef = useRef(null);

	const handleStyledExport = async () => {
		if (reportRef.current) {
			await downloadStyledPdf(reportRef.current); // saves styled.pdf
		}
	};

	return (
		<>
			<button onClick={handleStyledExport}>Export Styled PDF</button>
			<div ref={reportRef}>
				<ReportTemplate />
			</div>
		</>
	);
}

```

- **Drawbacks** — The PDF is an image, so text can’t be searched or selected. For vector text, fall back to `doc.html()`.

### Generate paginated tables with jspdf-autotable

If you export spreadsheets or invoices, install the [`jspdf-autoTable`](https://www.npmjs.com/package/jspdf-autotable) plugin:

```bash

npm i jspdf-autotable

```

```jsx

import jsPDF from 'jspdf';
import { autoTable } from 'jspdf-autotable';

export default function App() {
	// 1. Build your rows (array-of-arrays)
	const dataArray = [
		['Ada Lovelace', 'ada@example.com', 'UK'],
		['Grace Hopper', 'grace@navy.mil', 'USA'],
		['Alan Turing', 'alan@bletchley.gov', 'UK'],
	];

	const pdf = new jsPDF();
	autoTable(pdf, {
		head: [['Name', 'Email', 'Country']],
		body: dataArray,
		styles: { fontSize: 10 },
	});
	pdf.save('table.pdf');
}

```

`head` defines the header row, and `body` supplies one nested array per data line. `autoTable` sizes columns, paginates overflow rows, and repeats the header on every new page. The `styles` object applies global cell formatting — here, the font size is 10px.

### Encrypt and password-protect your PDF (built in)

Since jsPDF v2.5, [encryption is native](https://parallax.github.io/jsPDF/docs/jsPDF.html) and no extra plugin is required. Supply a `userPassword`, an `ownerPassword`, and a permissions array when you instantiate the document.

#### Basic encryption

```jsx

import jsPDF from 'jspdf';

const doc = new jsPDF({
	encryption: {
		userPassword: 'user123', // required to open the file
		ownerPassword: 'admin456', // controls editing rights
		permissions: [
			'print',
			'modify',
			'copy',
			'annot-forms', // allowed actions
		],
	},
});

doc.text('Top-secret numbers', 20, 20);
doc.save('protected.pdf');

```

- **Algorithm** — 128-bit AES is the default (stronger than the old RC4-40).

- **Permissions** — Omit an item to disallow it. Available flags: `print`, `print-low-resolution`, `modify`, `copy`, `annot-forms`, `fill-forms`.

#### Granular permissions

Limit users to low-resolution printing only:

```jsx

const doc = new jsPDF({
	encryption: {
		userPassword: 'viewer',
		ownerPassword: 'masterkey',
		permissions: ['print-low-resolution'],
		encryptionType: 128, // AES-128 (default)
	},
});

```

#### Change encryption strength

```jsx

const legacyDoc = new jsPDF({
	encryption: {
		userPassword: 'secret',
		encryptionType: 40, // RC4-40 — smaller file, legacy compatibility
	},
});

```

**Best practice** — Stick with AES-128 for modern apps. Use RC4-40 only when you must support very old PDF readers.

### Add metadata to your PDF

```jsx

import { useRef } from 'react';
import jsPDF from 'jspdf';
import ReportTemplate from './ReportTemplate';

export default function App() {
	const reportRef = useRef(null);

	const handleExport = async () => {
		if (reportRef.current) {
			const pdf = new jsPDF({
				orientation: 'portrait',
				unit: 'px',
				format: 'a4',
			});

			await pdf.html(reportRef.current, {
				async callback(doc) {
					// Set PDF metadata properties.
					doc.setProperties({
						title: 'Sales report 2025',
						subject: 'Quarterly numbers',
						author: 'ACME Corp',
						keywords: 'react, pdf, sales',
						creator: 'React app',
					});

					// Save the PDF file.
					doc.save('report.pdf');
				},
			});
		}
	};

	return (
		<>
			<button onClick={handleExport}>Export PDF</button>
			<div ref={reportRef}>
				<ReportTemplate />
			</div>
		</>
	);
}

```

`doc.setProperties()` embeds metadata (title, subject, author, keywords, creator) into the PDF. Viewers like Acrobat display these fields and search engines can index them. `doc.save("report.pdf")` then streams the file to the browser as a download.

### Alternative: Build PDFs with @react-pdf/renderer

If you need flexbox layouts, external fonts, and vector text without html2canvas, [`@react-pdf/renderer`](https://react-pdf.org/) takes a different approach — you define your PDF as React components instead of converting existing HTML.

Install it:

```bash

npm i @react-pdf/renderer

```

Create a PDF document component:

```jsx

import {
	Document,
	Page,
	Text,
	View,
	StyleSheet,
	PDFDownloadLink,
} from '@react-pdf/renderer';

const styles = StyleSheet.create({
	page: { padding: 40 },
	title: { fontSize: 24, marginBottom: 12 },
	row: {
		flexDirection: 'row',
		justifyContent: 'space-between',
		marginBottom: 6,
	},
});

function InvoicePdf({ items }) {
	return (
		<Document>
			<Page size="A4" style={styles.page}>
				<Text style={styles.title}>Invoice</Text>
				{items.map((item, i) => (
					<View key={i} style={styles.row}>
						<Text>{item.name}</Text>
						<Text>${item.price}</Text>
					</View>
				))}
			</Page>
		</Document>
	);
}

// Use `PDFDownloadLink` in your app to trigger a download.
export default function App() {
	const items = [
		{ name: 'Widget A', price: 25 },
		{ name: 'Widget B', price: 40 },
	];

	return (
		<PDFDownloadLink
			document={<InvoicePdf items={items} />}
			fileName="invoice.pdf"
		>
			{({ loading }) =>
				loading? 'Generating...' : 'Download Invoice PDF'
			}
		</PDFDownloadLink>
	);
}

```

#### Tradeoffs vs. jsPDF

- **Pros** — Supports flexbox, custom fonts via `Font.register()`, and vector text out of the box. No html2canvas needed.

- **Cons** — You can’t convert existing HTML/DOM elements. You must rewrite your layout using React PDF’s own `<View>`, `<Text>`, and `<Image>` primitives. Heavier bundle than jsPDF.

Use `@react-pdf/renderer` when you’re building a PDF from scratch (invoices, reports). Use jsPDF when you want to export an existing HTML page as-is.

## Comparing HTML-to-PDF libraries for React

| Use case                       | jsPDF | @react-pdf/renderer | Puppeteer / Playwright | pdf-lib | **Nutrient React Viewer** |
| ------------------------------ | ----- | ------------------- | ---------------------- | ------- | ------------------------- |
| **Client-only export**         | ✅     | ✅                   | ❌ (server)             | ✅       | ✅                         |
| **External CSS**               | ❌     | ❌                   | ✅                      | ❌       | —*                        |
| **Vector text**                | ✅     | ✅                   | ✅                      | ✅       | ✅                         |
| **Complex layout (flex/grid)** | ❌     | ✅                   | ✅                      | ❌       | ✅                         |
| **Password protection**        | ✅     | ❌                   | ✅                      | ❌       | ✅                         |
| **File size**                  | Small | Medium              | Large                  | Small   | Medium                    |
| **Learning curve**             | Low   | Medium              | Medium                 | Medium  | Medium                    |

\* The Nutrient React viewer renders existing PDF content, so external CSS limitations don’t apply.

## Conclusion

jsPDF handles basic HTML-to-PDF export in React, including custom fonts, encryption, and styled-component capture. When you need viewing, searching, commenting, redacting, or signing, the [Nutrient React viewer](https://www.nutrient.io/sdk/web/getting-started/react-vite.md) works with any React project and includes 30+ tools and a customizable UI.

For server-side automation, the [DWS Processor API](https://www.nutrient.io/api/processor-api/) provides the same capabilities, and the open source [MCP server](https://www.nutrient.io/blog/nutrient-dws-mcp-release/) lets AI assistants call those endpoints using natural-language prompts.

For other approaches, see our blogs on [HTML to PDF in JavaScript](https://www.nutrient.io/blog/html-to-pdf-in-javascript/) and [generating PDFs from HTML with Node.js](https://www.nutrient.io/blog/how-to-generate-pdf-from-html-with-nodejs.md).

Start a [free trial](https://www.nutrient.io/try/) or [launch the live demo](https://www.nutrient.io/demo/hello) to try the viewer.

## FAQ

#### What advantages does the Nutrient React viewer offer over jsPDF?

jsPDF handles basic HTML-to-PDF export. Beyond that, its feature set is limited.
The Nutrient React viewer renders PDFs with high fidelity and supports full-text search, annotation, redaction, digital signatures, form filling, and permission controls — all in one package. If your project needs more than static exports, Nutrient covers those use cases.

#### Can jsPDF use my existing CSS files?

Not directly. `html()` only sees styles already in the DOM. Inline your critical rules, or rasterize with `html2canvas`. For accurate CSS rendering, use a headless browser or the Nutrient React viewer instead.

#### How do I add password protection with jsPDF?

Since v2.5, you can pass a `userPassword`, an `ownerPassword`, and a permissions array when creating a document. Refer to the section on encrypting and password protection above.

#### Does the Nutrient React viewer work offline?

Yes. The viewer itself is a client-side bundle. Cloud features (like the
Processor API) are optional and can be self-hosted for fully on-premises
deployments.
---

## Related pages

- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [Html In Pdf Format](/blog/html-in-pdf-format.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdfjs Coordinate Systems Pdf To Screen](/blog/pdfjs-coordinate-systems-pdf-to-screen.md)
- [Pdfjs Navigation Zoom Rotation](/blog/pdfjs-navigation-zoom-rotation.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Process Flows](/blog/process-flows.md)
- [or](/blog/sample-blog-updated.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)

