HTML to PDF Best Practices: Page Breaks, Fonts, and Print CSS
A practical guide to producing polished, professional PDF output from HTML — controlling page layout, embedding fonts reliably, managing dynamic content, and setting up headers and footers that work every time.
Use a Dedicated Print Stylesheet
The single biggest improvement you can make to HTML-to-PDF output is a dedicated print stylesheet using the @media print block. This lets you maintain one HTML template that renders correctly in the browser and produces clean PDF output without navigation, sidebars, or interactive elements.
@media print {
/* Hide browser UI elements */
nav, .sidebar, .chat-widget, .cookie-banner,
.no-print, footer { display: none !important; }
/* Ensure full-width content */
.container { max-width: 100%; padding: 0; }
/* Control text size for print */
body { font-size: 11pt; line-height: 1.5; color: #000; }
/* Preserve links but show URL */
a[href]:after { content: " (" attr(href) ")"; font-size: 9pt; opacity: 0.6; }
a[href^="#"]:after { content: ""; } /* Skip internal links */
}
Set body font size in pt rather than px for print output. 11pt maps to approximately 15px at 96dpi, which is readable at standard zoom levels. Use color: #000 to ensure text is fully black rather than near-black grays that can appear washed out in PDF output.
Controlling Page Breaks
Uncontrolled page breaks are the most common complaint about HTML-to-PDF output. Tables split mid-row, headings appear at the bottom of a page separated from their content, and code blocks are cut in half. Fix these with CSS page break properties:
@media print {
/* Force a new page before each major section */
h1, .page-break-before { page-break-before: always; }
/* Never break inside these elements */
tr, pre, code, blockquote,
.invoice-line, .card { page-break-inside: avoid; }
/* Keep headings with the content that follows */
h2, h3, h4 {
page-break-after: avoid;
break-after: avoid;
}
/* For modern browsers — use break-* properties */
table { break-inside: avoid; }
thead { display: table-header-group; } /* Repeat table headers */
}
Use both page-break-* and break-* properties for maximum compatibility. break-inside: avoid is the modern standard but page-break-inside: avoid is supported by more rendering engines. Chromium respects both.
The display: table-header-group trick on thead causes the table header row to repeat at the top of every page when a table spans multiple pages — essential for data tables in reports and invoices.
Font Embedding and Typography for PDF
Fonts in PDFs work differently from web fonts. For best results with SnapAPI's Chromium-based PDF engine, use Google Fonts via the standard link tag — Chromium loads and embeds them correctly. Self-hosted fonts also work if they are accessible from your server at the time of capture.
<!-- In your HTML head -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap">
<style>
body { font-family: 'Inter', -apple-system, sans-serif; }
@media print {
/* Force font rendering in PDF */
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
</style>
The -webkit-print-color-adjust: exact and print-color-adjust: exact properties tell Chromium to preserve background colors and images in print mode. Without these, Chromium strips background colors to save printer ink — resulting in PDFs that look completely different from the browser view.
Avoid System Fonts in PDFs
Do not rely on system fonts like -apple-system or BlinkMacSystemFont as your primary font in PDF templates. These resolve to different typefaces depending on the operating system Chromium runs on. Always specify an explicit web font as the first entry in your font stack, with a generic family as fallback. This ensures consistent typography across all PDF captures regardless of server OS.
Headers, Footers, and Page Numbers
SnapAPI's PDF endpoint accepts header_template and footer_template parameters that inject HTML into the margin area of every page. These templates run in an isolated context with no access to the main document's styles, so you must set all CSS properties explicitly:
{
"url": "https://app.example.com/report/q1-2026",
"page_size": "A4",
"margin_top": "20mm",
"margin_bottom": "20mm",
"header_template": "<div style='font-family:Arial;font-size:9pt;color:#666;padding:0 10mm;width:100%;display:flex;justify-content:space-between'><span>Q1 2026 Report</span><span>Confidential</span></div>",
"footer_template": "<div style='font-family:Arial;font-size:9pt;color:#666;text-align:center;width:100%'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></div>",
"print_background": true
}
Set margin_top and margin_bottom large enough to accommodate your header and footer height. A 20mm margin gives enough room for a single line of text with comfortable padding. If the header overlaps the main content, increase the margin value.
Handling Dynamic Content and JavaScript
If your PDF template loads data via JavaScript — charts rendered with D3.js, tables populated via API calls, or dashboards built with React — use the wait_for parameter to delay capture until the content is fully rendered:
{
"url": "https://app.example.com/report?token=abc123",
"wait_for": ".chart-rendered",
"delay": 1000,
"page_size": "A4",
"print_background": true
}
Add a chart-rendered class to your chart container after the render completes. The API waits until it sees that class in the DOM before triggering PDF generation. The delay parameter adds an additional millisecond buffer after the selector appears — useful for animations that need to complete before capture.
Generate professional PDFs with SnapAPI at snapapi.pics — 200 free captures per month, no credit card required. The PDF endpoint supports all parameters discussed here and is available on every plan including the free tier.