All tools run in your browser — your files never leave your device.
All tools154

Developer & Security

Controlling where the page breaks in an HTML-to-PDF export

Browsers paginate HTML by falling off the bottom of the page. CSS has properties to control that, they are well supported, and they are almost never present in the stylesheets that need them.

The short answer. Use break-inside: avoid to stop an element splitting across pages, break-before and break-after to force or prevent breaks at specific points, and orphans and widows to control stranded lines. For repeating table headers use thead, which browsers repeat automatically on each page when a table breaks. These belong in a print stylesheet, so screen layout is unaffected.

The four properties that do the work

The four properties that do the work
PropertyWhat it doesTypical use
break-inside: avoidKeeps an element wholeCards, table rows, figures
break-after: avoidNo break directly afterHeadings
break-before: pageForces a new pageChapter or section starts
orphans / widowsMinimum lines each side of a breakBody paragraphs

These replace the older page-break-inside family, which browsers still honour as aliases. New stylesheets should use the unprefixed break-* names; keeping the old ones alongside costs nothing if you support very old renderers.

A print stylesheet that fixes most of it

The following handles the common failures — split rows, stranded headings, broken cards and lone lines. It goes inside a print media query so nothing changes on screen.

  • Headings stay with their content: h1, h2, h3 { break-after: avoid; }
  • Rows and cards stay whole: tr, .card, figure { break-inside: avoid; }
  • No stranded lines: p { orphans: 3; widows: 3; }
  • Table headers repeat: put the header row in <thead> — no CSS needed.
  • New page per section: .chapter { break-before: page; }
  • Page margins: @page { margin: 20mm; }

The thead one surprises people. Browsers repeat a proper table header on every page automatically — but only if the header row is inside a thead element rather than being the first tr in the tbody. A great many "my table header does not repeat" problems are that single piece of markup.

Why break-inside sometimes does nothing

It is well supported, so when it fails the cause is usually the surrounding layout rather than the property.

Fragmentation inside flex and grid containers has historically been the weak point. Chrome improved grid fragmentation considerably, but break-inside: avoid on a flex item is still unreliable across engines. If a card layout ignores the property, the flex or grid container is the first thing to test — switch that section to block or table display in the print stylesheet and it usually starts working.

  • The element is taller than a page. It has to break somewhere; the property is a request, not a guarantee.
  • It is a flex or grid item. Fragmentation support is patchy. Change the display mode for print.
  • An ancestor is overflow: hidden or a new formatting context. This frequently prevents fragmentation entirely.
  • The renderer is not a browser. wkhtmltopdf is based on a very old WebKit and supports almost none of this.

That last point matters more than the rest combined, and it is covered below.

Testing without printing anything

Guessing at print layout by exporting repeatedly is slow. Both major browsers can render print layout live.

In Chrome DevTools, open the command palette with Ctrl+Shift+P (Cmd+Shift+P on macOS), type "rendering", and set "Emulate CSS media type" to print. The page now renders with your print stylesheet applied and you can inspect and edit it normally.

Firefox has a similar toggle in the Rules panel of the inspector. This is the single biggest speed-up available for print CSS work — the alternative is a print-preview loop with no inspector, which is how most people do it and why print stylesheets tend to stay broken.

The renderer matters more than the CSS

If you generate PDFs on a server, what generates them determines what CSS you can rely on.

Headless Chrome via Puppeteer or Playwright is a current browser engine, so everything above works and matches what you saw in DevTools. wkhtmltopdf is built on a WebKit fork from around 2012 — no flexbox, no grid, minimal fragmentation support — and the project has been unmaintained for some time. A large number of "the CSS works in my browser but not in the PDF" reports are wkhtmltopdf.

The renderer matters more than the CSS
RendererEngineFragmentation CSS
Headless ChromeCurrent ChromiumFull
Playwright / PuppeteerCurrent Chromium or FirefoxFull
wkhtmltopdfWebKit c. 2012Minimal
WeasyPrintOwn engine, print-focusedGood, plus paged-media features
Browser Print to PDFWhatever the user hasFull, but user-dependent

WeasyPrint is worth knowing about for documents rather than web pages. It implements more of the CSS paged-media specification than browsers do — running headers, page counters, named pages — which is exactly what invoices and reports need.

Things that only break in print

  • Backgrounds vanish. Browsers drop background colours and images by default. print-color-adjust: exact asks to keep them; the user can still override it.
  • Links lose their destination. If the PDF will be read on paper, a[href]::after { content: " (" attr(href) ")" } makes them visible.
  • Sticky and fixed elements repeat or vanish. Set them to position: static for print.
  • Web fonts fail to load in a headless render. Wait for document.fonts.ready before generating, or text reflows against a fallback.
  • Lazy-loaded images are blank. Nothing scrolled, so nothing loaded. Disable lazy loading for the print render.

The font one is the most common headless-Chrome bug and the least obvious, because the layout is subtly different rather than visibly broken. Awaiting document.fonts.ready before calling page.pdf() resolves it.

Frequently asked questions

How do I stop a table row splitting across pages?

Apply break-inside: avoid to the tr element in a print stylesheet. If the row is taller than a page it will still break — the property is a request the renderer honours where it can, not a guarantee.

How do I repeat a table header on every page?

Put the header row inside a thead element. Browsers repeat it automatically when the table spans pages, with no CSS involved. Most reports of headers not repeating come down to the header being a plain tr inside tbody.

Why does break-inside: avoid not work on my cards?

Usually because they are flex or grid items, where fragmentation support remains patchy. Switch that container to block or table display inside the print media query. An ancestor with overflow: hidden can also prevent fragmentation entirely.

How do I force a page break before a section?

break-before: page on the element. Use break-after: page for the opposite. Both need to be on a block-level element in normal flow to be reliable.

Why do backgrounds disappear in the PDF?

Browsers omit background colours and images when printing to save ink. print-color-adjust: exact (with the -webkit- prefix for older Chrome) asks the renderer to keep them, though a user printing on paper can still override it.

Why does my CSS work in the browser but not in the generated PDF?

Almost always the renderer. wkhtmltopdf uses a WebKit fork from around 2012 with no flexbox, no grid and minimal fragmentation support. Headless Chrome via Puppeteer or Playwright uses a current engine and matches what you see in DevTools.

How can I preview print layout without exporting?

In Chrome DevTools press Ctrl+Shift+P, type "rendering", and set Emulate CSS media type to print. The page renders with the print stylesheet applied and stays fully inspectable. Firefox has the same toggle in the inspector.

Stop reading, start doing

Every tool in this guide is free.

154 browser-based utilities. No account, no upload, and no file size limit — your files are processed on your own device and never sent anywhere.

Browse all 154 tools