Single-page PDFs are straightforward, but real-world documents — contracts, reports, manuals, catalogs — span dozens or even hundreds of pages. Managing page breaks, keeping headers and footers consistent, and adding page numbers requires a solid understanding of CSS print styles and PDF API features. This guide covers everything you need to produce professional multi-page PDFs.
CSS Page Break Rules
CSS provides several properties to control where page breaks occur. The modern spec uses break-before, break-after, and break-inside, while the legacy properties page-break-before, page-break-after, and page-break-inside are still widely supported:
Force Page Breaks
/* Start each chapter on a new page */
.chapter { break-before: page; }
/* Alternative legacy syntax (wider browser support) */
.chapter { page-break-before: always; }
/* Force a break after the table of contents */
.toc { break-after: page; }
Prevent Unwanted Breaks
/* Keep headings with their following content */
h2, h3 { break-after: avoid; }
/* Don't break inside cards, figures, or table rows */
.card, figure, tr { break-inside: avoid; }
/* Keep at least 3 lines before/after a break (widows and orphans) */
p { widows: 3; orphans: 3; }
Table-Specific Breaks
Long tables are a common pain point. Here is a pattern that keeps the header visible on every page:
/* The browser will repeat thead on each page */
table { width: 100%; border-collapse: collapse; }
thead { display: table-header-group; }
tfoot { display: table-footer-group; }
tr { break-inside: avoid; }
/* Ensure table does not break in awkward places */
table { break-inside: auto; }
Headers and Footers with TongoRender
TongoRender supports Chromium's built-in header and footer templates. These are rendered separately from the main content and repeated on every page:
const response = await fetch('https://api.tongorender.io/v1/pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.TONGORENDER_API_KEY,
},
body: JSON.stringify({
html: documentHtml,
format: 'A4',
margin: { top: '25mm', bottom: '20mm', left: '15mm', right: '15mm' },
displayHeaderFooter: true,
headerTemplate: `
<div style="font-size:9px; color:#666; width:100%; padding:0 15mm; display:flex; justify-content:space-between;">
<span>Acme Corp — Confidential</span>
<span>Annual Report 2026</span>
</div>
`,
footerTemplate: `
<div style="font-size:9px; color:#666; width:100%; padding:0 15mm; display:flex; justify-content:space-between;">
<span>Generated on 2026-03-15</span>
<span>Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
</div>
`,
}),
});
Built-in Template Variables
Chromium provides these CSS classes that are automatically replaced with values:
pageNumber— Current page numbertotalPages— Total number of pagesdate— Formatted print datetitle— Document title from<title>url— Document URL
Page Numbers with Custom Styling
For more control over page number styling, use the footer template with CSS:
footerTemplate: `
<div style="width:100%; text-align:center; font-size:10px; font-family:Arial;">
<span style="
background: #1a1a2e;
color: white;
padding: 2px 10px;
border-radius: 10px;
font-weight: bold;
">
<span class="pageNumber"></span>
</span>
</div>
`
Complete Multi-Page Document Example
Here is a full example that combines all techniques — a report with a cover page, table of contents, chapters, and a long data table:
function buildReport(data) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body { font-family: 'Georgia', serif; color: #333; line-height: 1.6; margin: 0; padding: 30px; }
/* Cover page */
.cover { height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; }
.cover h1 { font-size: 36pt; color: #1a1a2e; margin-bottom: 10px; }
.cover .subtitle { font-size: 16pt; color: #666; }
.cover { break-after: page; }
/* Table of contents */
.toc { break-after: page; }
.toc-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px dotted #ccc; }
/* Chapters */
.chapter { break-before: page; }
h2 { color: #1a1a2e; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px; break-after: avoid; }
h3 { color: #2d3748; break-after: avoid; }
/* Tables */
table { width: 100%; border-collapse: collapse; margin: 15px 0; }
thead { display: table-header-group; background: #1a1a2e; color: white; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #e2e8f0; }
tr { break-inside: avoid; }
tr:nth-child(even) { background: #f7fafc; }
/* Prevent orphans and widows */
p { orphans: 3; widows: 3; }
@media print {
.no-print { display: none; }
}
</style>
</head>
<body>
<div class="cover">
<h1>${data.title}</h1>
<div class="subtitle">${data.subtitle}</div>
<div style="margin-top:30px;color:#999">${data.date}</div>
</div>
<div class="toc">
<h2>Table of Contents</h2>
${data.chapters.map((ch, i) => \`
<div class="toc-item"><span>${i + 1}. ${ch.title}</span></div>
\`).join('')}
</div>
${data.chapters.map(ch => \`
<div class="chapter">
<h2>${ch.title}</h2>
${ch.content}
</div>
\`).join('')}
</body>
</html>`;
}
Common Pitfalls
- Margins too small for headers/footers — The top and bottom margins must be large enough to contain the header and footer templates. If your header is 15mm tall, set the top margin to at least 20mm.
- Forgetting
display: table-header-group— Without this, table headers do not repeat on subsequent pages. - Using
position: fixedfor headers — Fixed positioning does not work for repeating headers in print. Use the API's header/footer templates instead. - Background colors not printing — Set
printBackground: truein the API options. - Ignoring orphans and widows — A single line of a paragraph stranded at the top or bottom of a page looks unprofessional.
With proper CSS print styles and TongoRender's header/footer support, you can generate professional multi-page documents that rival dedicated desktop publishing tools.
Generate multi-page PDFs with TongoRender — 100 free renders per month, no credit card required.