How to export HTML to PDF

The browser print dialog already exports PDF, and it is the highest fidelity option you have. Most bad PDFs come from a missing print stylesheet rather than from the wrong tool.

To export HTML to PDF, open the page in a browser, press Ctrl+P, and choose Save as PDF as the destination. That is the highest fidelity route available, because the browser that drew the page is the one writing the file.

There are three ways to do it, and this covers how to export HTML to PDF from each of them, plus the print stylesheet that decides where pages break.

Most complaints about PDF export are not about the tool. They are about a page that was never given instructions for paper.

The browser print dialog with Save as PDF selected as the destination and the preview pane showing page one.
The browser print dialog with Save as PDF selected as the destination and the preview pane showing page one.

How to export HTML to PDF: three routes

Route Fidelity Setup Repeatable
Print dialog Highest None No
Headless browser Same engine, scripted Node and a browser Yes
Canvas library Raster, text not selectable A script tag Yes

The first two use the same renderer, so they agree. The third draws the page into an image and wraps it, which is why the text in those PDFs cannot be selected.

Route 1: the print dialog

Two settings decide whether the output is usable, and both are hidden under More settings.

  • Background graphics. Off by default to save ink. Coloured panels, dark themes and table row striping all print white without it.
  • Margins. Default margins are generous. Set them to none if your CSS already handles spacing, otherwise you get spacing twice.

Also check Headers and footers. On, the browser adds the page title and address at the edges, which looks unfinished in a document you are sending to a client.

Set the scale to 100 rather than Fit to page width. Fit silently shrinks the whole document to avoid one overflowing element, and the result is a report in small type with no explanation for it.

Choose the paper size before you check the preview, not after. Switching between A4 and Letter moves every page break, so any break fixing you did at the wrong size has to be redone.

Route 2: a headless browser

For a report that is produced on a schedule, script it. The output matches the dialog because it is the same engine.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('file:///C:/reports/weekly.html');
await page.emulateMedia({ media: 'print' });
await page.waitForLoadState('networkidle');
await page.pdf({
  path: 'weekly.pdf',
  format: 'A4',
  printBackground: true,
  margin: { top: '16mm', bottom: '16mm', left: '14mm', right: '14mm' },
});
await browser.close();

emulateMedia is the line people forget. Without it, your @media print rules never apply and the PDF shows the screen layout.

printBackground: true is the scripted equivalent of the background graphics checkbox.

Route 3: a canvas library

jsPDF combined with html2canvas rasterises the page and places the image into a PDF. It runs entirely in the browser with no server.

The tradeoff is that the result is a picture. Text is not selectable, not searchable, and looks soft when printed. Long pages have to be sliced across pages by hand, and slices land mid line.

Use it when the export has to happen client side and the content is a single visual card. Otherwise prefer the print route.

The print stylesheet does the real work

A @media print block is what turns a screen page into a document. Four rules cover most of it.

@media print {
  nav, .sidebar, .no-print { display: none; }

  @page { size: A4; margin: 16mm 14mm; }

  h2, h3 { break-after: avoid; }
  table, figure, .card { break-inside: avoid; }

  body { print-color-adjust: exact; }
}

break-after: avoid on headings stops a heading being stranded at the bottom of a page. break-inside: avoid keeps a table or a card whole.

print-color-adjust: exact asks the browser to keep backgrounds even when the user has not ticked the box. Print stylesheets covers the rest.

A table split across two pages, next to the same table after break-inside avoid is applied.
A table split across two pages, next to the same table after break-inside avoid is applied.

The five problems you will actually hit

  1. Everything prints white. Background graphics is off, or print-color-adjust is missing.
  2. A table splits mid row. Add break-inside: avoid to the rows. Keep the header in a <thead> so it repeats on each page.
  3. Charts are blank. The script had not drawn when the print snapshot was taken. In a headless run, wait for the chart element rather than for a fixed delay.
  4. Content is cut off at the right. A fixed pixel width wider than the paper. Set width: 100% and max-width: none inside the print block.
  5. Fonts are wrong. The web font had not loaded. Await document.fonts.ready before calling pdf, or embed the font.

Checking the file

Look at the preview pane before saving, and scroll all of it rather than glancing at page one. Breaks go wrong in the middle of documents, not at the start.

Then open the saved PDF and try to select a paragraph. If you cannot, something rasterised the page and the file is an image in a wrapper.

Check the file size while you are there. A three page report over five megabytes almost always means uncompressed images rather than anything wrong with the export.

And read the first line of each page. Headings stranded at a page foot and rows separated from their header are the two defects readers notice immediately.

Text being selected inside the exported PDF, showing that it is real text rather than an image.
Text being selected inside the exported PDF, showing that it is real text rather than an image.

When PDF is the wrong deliverable

A PDF is fixed. That is the point when the document is final, and a liability when it is not.

Numbers freeze, interactive charts become pictures, and every copy circulating is a version someone might quote. PDF versus an HTML page sets out the comparison.

Send a PDF when paper, filing or a signature is the destination. Send a link when the content still moves.

Paste the HTML into a NOS document and the page renders as written, with its own address, and the reader can still export their own PDF from it with Ctrl+P. Turning HTML into a link is that step on its own.

That combination covers both needs: the current version lives at one address, and anyone who needs paper makes it themselves from the page in front of them.

Questions people ask

How do I export an HTML page to PDF?

Open the page and press Ctrl+P, or Cmd+P on a Mac, then choose Save as PDF as the destination. Tick Background graphics if the page has coloured panels, since the browser strips them by default.

Why are my background colours missing in the PDF?

The print dialog disables background graphics to save ink. Tick the option under More settings, or set print-color-adjust to exact in your print stylesheet so the colours are preserved.

How do I stop a table splitting across pages?

Use break-inside: avoid on the rows or on the whole table in a print media block. Add thead repetition by keeping the header inside a thead element, which browsers repeat on each page.

Can I export to PDF from a script?

Yes. A headless Chromium exposes a pdf method that takes a format, margins and a printBackground flag. That is the route for a report generated on a schedule rather than by hand.

Keep reading