CSS @page Rule for PDF: Size, Margins, Headers & Print Fixes
When generating PDFs from HTML, how do you control paper size, margins, and header/footer placement? The answer is the CSS @page rule.
@page is the core of the CSS Paged Media module, designed specifically for print and PDF output styling. This guide walks you through the full @page specification — from basic syntax to page pseudo-classes, named pages, and margin boxes — everything you need for professional PDF generation.
You can test every example in this article using the FUNBREW PDF Playground.
Basic @page Syntax
The @page rule defines styles that apply to the entire page box. The two most common properties are size and margin.
Setting Page Size
/* A4 portrait */
@page {
size: A4;
}
/* A4 landscape */
@page {
size: A4 landscape;
}
/* Explicit dimensions */
@page {
size: 210mm 297mm;
}
/* US Letter */
@page {
size: letter;
}
The size property accepts keywords like A3, A4, A5, B4, B5, letter, and legal. You can also specify width and height directly using mm, cm, or in units.
Setting Margins
@page {
size: A4;
margin: 20mm;
}
/* Different margins for each side */
@page {
size: A4;
margin: 25mm 20mm 30mm 20mm; /* top right bottom left */
}
Margins have a significant impact on PDF layout. For invoices, 20mm on all sides is typical. For reports, 25mm top/bottom with 20mm left/right works well.
Tip: The
@pagemargin is independent of the HTML body margin. The page margin defines the outer boundary, and the content area lives inside it where normal CSS applies.
Page Pseudo-Classes — :first, :left, :right, :blank
@page supports pseudo-classes that let you apply different styles based on page position.
:first — Different Layout for the First Page
Useful for cover pages, invoice headers, or any first-page customization.
/* Base settings for all pages */
@page {
size: A4;
margin: 25mm 20mm;
}
/* Larger margins on the cover page */
@page :first {
margin: 40mm 30mm;
}
:left / :right — Spread (Facing) Pages
For booklets and bound documents where left and right pages need different margins.
/* Left pages (even) */
@page :left {
margin-left: 30mm;
margin-right: 20mm;
}
/* Right pages (odd) */
@page :right {
margin-left: 20mm;
margin-right: 30mm;
}
The wider inner margin accommodates the gutter (binding edge).
:blank — Empty Pages
Applies to blank pages inserted by break-before: left or break-before: right.
@page :blank {
@top-center {
content: ""; /* Hide header on blank pages */
}
}
Named Pages — Multiple Layouts in One Document
The page property lets you assign different page configurations to different elements.
/* Cover page definition */
@page cover {
size: A4;
margin: 0;
}
/* Content page definition */
@page content {
size: A4;
margin: 25mm 20mm;
}
/* Landscape chart page */
@page landscape-chart {
size: A4 landscape;
margin: 15mm;
}
Assign named pages to elements using the page CSS property:
.cover-page {
page: cover;
}
.main-content {
page: content;
}
.chart-section {
page: landscape-chart;
}
<div class="cover-page">
<h1>Annual Report 2026</h1>
</div>
<div class="main-content">
<h2>Chapter 1: Overview</h2>
<p>...</p>
</div>
<div class="chart-section">
<img src="chart.png" alt="Revenue trends" />
</div>
Named pages let you produce a single PDF where the cover has no margins, body pages use A4 portrait, and chart pages use A4 landscape — all from one HTML source.
Margin Boxes — Headers, Footers, and Page Numbers
Margin boxes are defined inside @page rules and place content in the page margin area. This is the CSS-native mechanism for PDF headers, footers, and page numbers.
Margin Box Positions
@top-left @top-center @top-right
┌──────────────────────────────────────┐
│ │
@left-top @right-top
│ │
@left-middle @right-middle
│ Content Area │
@left-bottom @right-bottom
│ │
└──────────────────────────────────────┘
@bottom-left @bottom-center @bottom-right
There are 16 margin box positions in total.
Adding Page Numbers
@page {
size: A4;
margin: 25mm 20mm 30mm 20mm;
@bottom-center {
content: counter(page);
font-size: 10pt;
color: #666;
}
}
/* "3 / 12" format */
@page {
@bottom-right {
content: counter(page) " / " counter(pages);
font-size: 9pt;
}
}
counter(page) returns the current page number and counter(pages) returns the total page count.
Header and Footer Example
@page {
size: A4;
margin: 30mm 20mm 25mm 20mm;
@top-left {
content: "Acme Corporation";
font-size: 8pt;
color: #999;
}
@top-right {
content: "Confidential";
font-size: 8pt;
color: #c00;
font-weight: bold;
}
@bottom-left {
content: "Published April 2026";
font-size: 8pt;
color: #999;
}
@bottom-right {
content: counter(page) " / " counter(pages);
font-size: 8pt;
}
}
/* No headers or footers on the cover */
@page :first {
@top-left { content: ""; }
@top-right { content: ""; }
@bottom-left { content: ""; }
@bottom-right { content: ""; }
}
Practical Example — Invoice with Cover Page
Let's combine everything into a real-world invoice PDF with a cover page.
/* === Page Setup === */
@page {
size: A4;
margin: 20mm;
@bottom-center {
content: counter(page);
font-size: 9pt;
color: #888;
}
}
@page cover {
margin: 0;
@bottom-center { content: ""; }
}
@page :first {
@bottom-center { content: ""; }
}
/* === Layout === */
body {
font-family: "Inter", "Helvetica Neue", sans-serif;
font-size: 10pt;
line-height: 1.6;
color: #333;
margin: 0;
padding: 0;
}
.cover {
page: cover;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
background: linear-gradient(135deg, #1a365d, #2563eb);
color: #fff;
text-align: center;
}
.cover h1 {
font-size: 28pt;
margin-bottom: 10mm;
}
/* Invoice table */
.invoice-table {
width: 100%;
border-collapse: collapse;
break-inside: avoid;
}
.invoice-table th,
.invoice-table td {
border: 1px solid #ddd;
padding: 8px 12px;
}
.invoice-table th {
background: #f5f5f5;
text-align: left;
}
.total-row {
font-weight: bold;
font-size: 12pt;
break-before: avoid;
}
<div class="cover">
<div>
<h1>Invoice</h1>
<p>April 2026</p>
</div>
</div>
<section class="invoice-body">
<h2>Invoice Details</h2>
<table class="invoice-table">
<thead>
<tr>
<th>Item</th>
<th>Qty</th>
<th>Unit Price</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr><td>PDF Generation API (Standard Plan)</td><td>1</td><td>$50.00</td><td>$50.00</td></tr>
<tr><td>Additional Requests (100 units)</td><td>2</td><td>$10.00</td><td>$20.00</td></tr>
</tbody>
<tfoot>
<tr class="total-row"><td colspan="3">Total</td><td>$70.00</td></tr>
</tfoot>
</table>
</section>
Send this HTML and CSS to the FUNBREW PDF API and you get a professional invoice with a branded cover page.
Multi-Section Report Example
For documents that need different layouts for the cover, table of contents, body, and appendix:
@page cover {
size: A4;
margin: 0;
}
@page toc {
size: A4;
margin: 30mm 25mm;
@top-center {
content: "Table of Contents";
font-size: 9pt;
color: #666;
}
}
@page main {
size: A4;
margin: 25mm 20mm;
@top-left {
content: "Monthly Report";
font-size: 9pt;
color: #666;
}
@bottom-right {
content: counter(page);
font-size: 9pt;
}
}
@page appendix {
size: A4 landscape;
margin: 15mm;
@bottom-center {
content: "Appendix - " counter(page);
font-size: 8pt;
}
}
.cover { page: cover; }
.toc { page: toc; }
.main { page: main; }
.appendix { page: appendix; }
Engine Compatibility
@page support varies significantly between PDF generation engines.
| Feature | Chromium (Headless) | wkhtmltopdf |
|---|---|---|
size |
Yes | Partial |
margin |
Yes | Yes |
:first |
Yes | No |
:left / :right |
Yes | No |
| Named pages | Yes | No |
| Margin boxes | Partial | No |
counter(page) |
Partial | No |
counter(pages) |
Partial | No |
Chromium-Based Engines
Chromium (used by Puppeteer, Playwright, and FUNBREW PDF) supports the core @page properties well. Margin box support has been improving since Chrome 131, though it is not yet complete.
Important caveat: When using Chromium's page.pdf(), specifying format or margin options overrides your CSS @page settings. If you want CSS to control the layout, either omit those API options or set preferCSSPageSize: true.
wkhtmltopdf
wkhtmltopdf uses an older WebKit engine with limited @page support. Page size is typically set via the --page-size CLI option, and headers/footers use --header-html / --footer-html options instead of CSS margin boxes.
wkhtmltopdf: Setting A4 Size with Zero Margins
The most reliable way to produce a full-bleed A4 PDF in wkhtmltopdf is to combine the --page-size flag with --margin-* flags set to 0, rather than relying on @page CSS:
wkhtmltopdf \
--page-size A4 \
--margin-top 0 \
--margin-right 0 \
--margin-bottom 0 \
--margin-left 0 \
input.html output.pdf
If you prefer to control layout entirely through CSS (for example, when migrating from wkhtmltopdf to a Chromium-based API), use @page with explicit margin: 0 and pass --disable-smart-shrinking to prevent automatic content scaling:
@page {
size: A4; /* 210mm × 297mm */
margin: 0;
}
body {
margin: 0;
padding: 0;
width: 210mm;
}
wkhtmltopdf --disable-smart-shrinking input.html output.pdf
For a deeper comparison of wkhtmltopdf and Chromium rendering differences, see "wkhtmltopdf vs Chromium Compared."
Common Mistakes and Fixes
1. Double Margins
/* WRONG: @page margin + body margin stack */
@page { margin: 20mm; }
body { margin: 20mm; }
/* CORRECT: Reset body margin */
@page { margin: 20mm; }
body { margin: 0; padding: 0; }
The @page margin defines the page boundary. Adding a body margin on top of that doubles the whitespace.
2. Page Size Not Applying
Chromium API options can override your @page { size } declaration.
// WRONG: format overrides @page
await page.pdf({ format: 'A4' });
// CORRECT: Let CSS @page control the size
await page.pdf({ preferCSSPageSize: true });
In FUNBREW PDF, you can set preferCSSPageSize in the request parameters. Check the API documentation for details.
3. Headers and Footers Not Showing
Margin box support is still limited in browsers. If they do not render, use this alternative approach:
/* Alternative: position: fixed for headers/footers */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 15mm;
font-size: 8pt;
color: #999;
border-bottom: 0.5pt solid #ddd;
}
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 10mm;
font-size: 8pt;
text-align: center;
color: #999;
}
@page {
margin-top: 25mm; /* Reserve space for header */
margin-bottom: 20mm; /* Reserve space for footer */
}
position: fixed works reliably in Chromium and repeats on every page.
4. Named Pages Not Triggering Page Breaks
Elements with different named pages must be siblings in the document flow for automatic page breaks to occur.
<!-- WRONG: Nested elements may not trigger breaks -->
<div class="wrapper">
<div class="cover-page">...</div>
<div class="main-content">...</div>
</div>
<!-- CORRECT: Top-level siblings -->
<div class="cover-page">...</div>
<div class="main-content">...</div>
5. Hiding the table-header-group (<thead>) on the First Page (Chromium)
When a <table> spans multiple pages in a Chromium-rendered PDF, the browser repeats the <thead> (which has display: table-header-group) on every page — including the first. If your design has a visible header row above the table on page one, the <thead> duplication creates an unwanted double header.
The cleanest fix is to use a @page :first rule together with a CSS class on the <thead>:
/* Hide thead only on the first page */
@page :first {
/* The page pseudo-class itself has no visibility property,
so we use a sibling selector trick via a first-page marker. */
}
/* Add a full-bleed marker element to detect the first page */
.first-page-marker {
display: block;
height: 0;
overflow: hidden;
}
/* Alternative: use JavaScript to add a class after render */
Because Chromium does not yet expose a first-page CSS selector that targets child elements, the most reliable production approach is to split the header row from the <thead> and render a standalone header <div> outside the <table>:
<!-- Standalone header visible only above the table on page 1 -->
<div class="table-visual-header">
<span>Name</span><span>Score</span><span>Date</span>
</div>
<!-- Table with an empty or hidden thead -->
<table>
<thead class="sr-only" aria-hidden="false">
<!-- Keep <thead> for accessibility and repeated-page headers -->
<tr><th>Name</th><th>Score</th><th>Date</th></tr>
</thead>
<tbody>
<tr><td>Jane Smith</td><td>95</td><td>2026-05-03</td></tr>
</tbody>
</table>
/* Hide the visual duplicate on page 1 only by matching
print media — Chromium honours print styles in PDF */
@media print {
.table-visual-header {
display: none; /* hide the standalone header on all pages */
}
}
/* Make thead repeat on subsequent pages but invisible on page 1
by keeping it in DOM but giving page-1 body a padding offset */
thead {
display: table-header-group; /* default — repeats on every page */
}
The simplest approach that works reliably across Chromium versions:
- Keep the
<thead>for semantic correctness and repeated-page display. - If you do not want the header text to appear on page one, render your own styled heading row as a
<div>outside the table, then setthead { visibility: hidden; }on the first page using JavaScript before the PDF API call.
// Before calling the PDF API: inject a style tag that hides thead
// only if the table starts at the top of page 1
const style = document.createElement('style');
style.textContent = `
/* Hide thead on first page when the table starts at the very top */
.no-first-page-header thead { visibility: hidden; height: 0; }
.no-first-page-header thead tr { line-height: 0; font-size: 0; }
`;
document.head.appendChild(style);
document.querySelector('table').classList.add('no-first-page-header');
For the full set of Chromium print CSS quirks (backgrounds, fonts, page breaks), see HTML to PDF CSS Tips.
Summary
The CSS @page rule is purpose-built for controlling the physical pages of printed and PDF output.
- Basics:
sizesets the paper dimensions,marginsets the page margins - Pseudo-classes:
:first,:left,:rightapply per-page styles - Named pages: Switch layouts within a single document
- Margin boxes: Place headers, footers, and page numbers with pure CSS
With a Chromium-based engine, the major @page features are well supported, letting you build sophisticated PDF layouts with CSS alone. Try your CSS live in the Playground.
For broader CSS techniques in PDF generation, see "HTML to PDF CSS Tips." For a full overview of PDF generation, check the "HTML to PDF Complete Guide." If something is not working as expected, the "PDF Troubleshooting Guide" has solutions for the most common issues.