CSS Print Stylesheets: The Complete Developer's Reference
Every time you generate a PDF from HTML, you're essentially telling the browser (or PDF engine) to "print" your web page onto fixed-size paper. Yet most CSS is written exclusively for screens — infinite scrolling, responsive breakpoints, hover effects. Print CSS is the forgotten sibling.
This article is a practical reference for everything print-related in CSS. Bookmark it — you'll come back to it every time you build a PDF template.
The @media print Query
The simplest way to add print-specific styles is with the @media print query:
/* Styles that only apply when printing (or generating PDF) */
@media print {
.no-print {
display: none !important;
}
body {
font-size: 11pt;
color: #000;
background: #fff;
}
a {
text-decoration: none;
color: #000;
}
}
But for serious PDF generation, @media print is just the beginning. The real power is in CSS Paged Media.
Units: What to Use for Print
Screen CSS uses px, rem, em, and viewport units (vw, vh). Print CSS should use physical units:
| Unit | Value | Use for |
|---|---|---|
pt |
1/72 of an inch | Font sizes (standard in print) |
mm |
Millimeters | Margins, spacing, dimensions |
cm |
Centimeters | Larger margins, page-level spacing |
in |
Inches | US paper formats, margins |
pc |
Pica (12pt) | Typography |
Why not px? A pixel has no fixed physical size — it depends on the device's DPI. On a 96 DPI screen, 1px ≈ 0.26mm. On a 300 DPI printer, 1px ≈ 0.085mm. Using mm or pt ensures consistent sizing across devices and PDF engines.
/* ❌ Screen-oriented sizing */
body {
font-size: 16px;
padding: 2rem;
}
/* ✅ Print-oriented sizing */
body {
font-size: 10pt;
padding: 10mm;
}
Exception: px is acceptable for borders and hairlines where you want the thinnest possible line:
.divider {
border-bottom: 0.5pt solid #ccc;
}
The @page Rule
The @page rule controls page-level properties: dimensions, margins, and (in supporting engines) margin boxes for headers and footers.
Page Size
@page {
size: A4; /* 210mm × 297mm */
}
@page {
size: letter; /* 8.5in × 11in */
}
@page {
size: A4 landscape; /* 297mm × 210mm */
}
@page {
size: 150mm 200mm; /* Custom dimensions */
}
Standard sizes: A3, A4, A5, B4, B5, letter, legal, ledger.
Page Margins
@page {
size: A4;
margin: 20mm; /* All sides */
}
@page {
size: A4;
margin: 15mm 20mm 25mm 20mm; /* Top Right Bottom Left */
}
Named Pages
You can define different page styles for different sections:
@page cover {
margin: 0; /* Full-bleed cover page */
}
@page landscape-data {
size: A4 landscape;
margin: 10mm;
}
@page standard {
size: A4;
margin: 20mm;
}
.cover-section {
page: cover;
}
.data-tables-section {
page: landscape-data;
}
Support: Named pages work in WeasyPrint and Prince, but NOT in Chromium/Puppeteer.
Page Selectors
@page :first {
margin-top: 40mm; /* Extra top margin on first page */
}
@page :left {
margin-left: 25mm; /* Wider inner margin for binding */
margin-right: 15mm;
}
@page :right {
margin-left: 15mm;
margin-right: 25mm; /* Wider inner margin for binding */
}
@page :blank {
/* Style for intentionally blank pages */
@top-center {
content: "This page intentionally left blank";
}
}
Page Break Control
Break Properties
CSS provides three properties for controlling page breaks:
/* break-before: controls breaks BEFORE the element */
h1 {
break-before: page; /* Always start on a new page */
}
h1:first-child {
break-before: auto; /* Don't add a blank first page */
}
/* break-after: controls breaks AFTER the element */
.chapter-summary {
break-after: page; /* New page after each summary */
}
h2 {
break-after: avoid; /* Don't leave heading alone at bottom */
}
/* break-inside: controls breaks WITHIN the element */
table {
break-inside: avoid; /* Don't split tables across pages */
}
.card {
break-inside: avoid; /* Keep cards together */
}
Break Values
| Value | Meaning |
|---|---|
auto |
Default — engine decides |
avoid |
Try to avoid breaking here |
page |
Force a page break |
left |
Force break so next content is on a left page |
right |
Force break so next content is on a right page |
column |
Force a column break (in multi-column layouts) |
avoid-page |
Avoid page break specifically |
avoid-column |
Avoid column break specifically |
Orphans and Widows
p {
orphans: 3; /* At least 3 lines at bottom of page */
widows: 3; /* At least 3 lines at top of page */
}
- Orphan: A paragraph's first few lines stranded at the bottom of a page
- Widow: A paragraph's last few lines stranded at the top of a page
Both are typographically undesirable. Setting both to 3 is a safe default.
Page Margin Boxes (Headers/Footers)
CSS Paged Media defines 16 margin boxes around each page. These are used for running headers, footers, page numbers, and other repeating content.
┌─────────────────────────────────────┐
│ @top-left │ @top-center │ @top-right│
├────────────┼─────────────┼──────────┤
│ │ │ │
│ @left-top │ │@right-top│
│ │ │ │
│ @left-mid │ PAGE │@right-mid│
│ │ CONTENT │ │
│ @left-bot │ │@right-bot│
│ │ │ │
├────────────┼─────────────┼──────────┤
│@bottom-left│@bottom-center│@bot-right│
└─────────────────────────────────────┘
Page Numbers
@page {
@bottom-right {
content: "Page " counter(page) " of " counter(pages);
font-size: 8pt;
color: #999;
}
}
Running Headers
@page {
@top-left {
content: "Company Name";
font-size: 8pt;
color: #666;
}
@top-right {
content: string(chapter-title);
font-size: 8pt;
font-style: italic;
}
}
h1 {
string-set: chapter-title content();
}
The string-set property captures the heading text, and string() retrieves it in the margin box. This gives you dynamic headers that change based on the current chapter.
First Page Without Header/Footer
@page :first {
@top-left { content: none; }
@top-right { content: none; }
@bottom-right { content: none; }
}
Support: Margin boxes work in WeasyPrint and Prince. Chromium/Puppeteer does NOT support them — you have to use Chrome's limited built-in header/footer API instead.
Generated Content for Print
Cross-References
/* Create a link with the URL visible */
a[href^="http"]::after {
content: " (" attr(href) ")";
font-size: 8pt;
color: #666;
}
/* Don't show URL for internal links */
a[href^="#"]::after {
content: none;
}
Footnotes (Prince and WeasyPrint)
.footnote {
float: footnote;
font-size: 8pt;
}
::footnote-call {
content: counter(footnote);
font-size: 7pt;
vertical-align: super;
}
::footnote-marker {
content: counter(footnote) ". ";
}
Table of Contents with Leaders
.toc-entry a::after {
content: leader('.') target-counter(attr(href), page);
}
This produces: Chapter 1: Introduction ............... 1
Support: Leaders and target-counter are Prince and WeasyPrint features.
Practical Print Patterns
Pattern 1: The Invoice
@page {
size: A4;
margin: 15mm;
}
@page :first {
margin-top: 10mm;
@bottom-center { content: none; }
}
@page {
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
font-size: 8pt;
color: #999;
}
}
body {
font-family: 'Inter', sans-serif;
font-size: 9pt;
line-height: 1.4;
color: #1a1a1a;
}
.invoice-header {
display: flex;
justify-content: space-between;
margin-bottom: 10mm;
padding-bottom: 5mm;
border-bottom: 0.5pt solid #e5e5e5;
}
table {
width: 100%;
border-collapse: collapse;
break-inside: auto;
}
thead {
display: table-header-group; /* Repeat on every page */
}
th {
text-align: left;
padding: 2mm 3mm;
font-size: 8pt;
text-transform: uppercase;
letter-spacing: 0.5pt;
border-bottom: 1pt solid #1a1a1a;
}
td {
padding: 2mm 3mm;
border-bottom: 0.25pt solid #e5e5e5;
}
tr {
break-inside: avoid;
}
.totals {
break-inside: avoid;
margin-top: 5mm;
text-align: right;
}
Pattern 2: The Report with Chapters
@page {
size: A4;
margin: 25mm 20mm 30mm 25mm;
@top-right {
content: string(section-title);
font-size: 8pt;
color: #666;
}
@bottom-center {
content: counter(page);
font-size: 8pt;
}
}
@page :first {
@top-right { content: none; }
@bottom-center { content: none; }
}
h1 {
break-before: page;
string-set: section-title content();
font-size: 24pt;
margin-bottom: 10mm;
}
h1:first-child {
break-before: auto;
}
h2 {
break-after: avoid;
font-size: 16pt;
margin-top: 8mm;
margin-bottom: 4mm;
}
h3 {
break-after: avoid;
}
figure {
break-inside: avoid;
text-align: center;
margin: 5mm 0;
}
figcaption {
font-size: 8pt;
font-style: italic;
color: #666;
}
Pattern 3: Labels and Cards (Multiple Per Page)
@page {
size: A4;
margin: 10mm;
}
.label-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 5mm;
}
.label {
width: 60mm;
height: 30mm;
padding: 3mm;
border: 0.25pt solid #ccc;
break-inside: avoid;
font-size: 8pt;
}
Browser/Engine Compatibility Quick Reference
| Feature | Chromium | WeasyPrint | Prince |
|---|---|---|---|
@page size |
✅ | ✅ | ✅ |
@page margin |
✅ | ✅ | ✅ |
@page :first |
❌ | ✅ | ✅ |
@page :left/:right |
❌ | ✅ | ✅ |
Named pages (page:) |
❌ | ✅ | ✅ |
| Margin boxes | ❌ | ✅ | ✅ |
counter(page) in margins |
❌ | ✅ | ✅ |
break-before: page |
✅ | ✅ | ✅ |
break-inside: avoid |
⚠️ | ✅ | ✅ |
orphans/widows |
⚠️ | ✅ | ✅ |
string-set |
❌ | ✅ | ✅ |
float: footnote |
❌ | ✅ | ✅ |
leader() |
❌ | ❌ | ✅ |
| CSS Grid | ✅ | ❌ | ✅ |
| Flexbox | ✅ | ✅ | ✅ |
| Multi-column | ✅ | ✅ | ✅ |
The Practical Takeaway
If you need full print CSS support (running headers, page counters, named pages), use WeasyPrint (free) or Prince (commercial).
If you need full web CSS support (Grid, complex JS), use Chromium/Puppeteer — but accept the print CSS limitations.
If you want both web-quality rendering and print-quality layout, consider an API service like PDF-API.io that combines the best of both worlds.
Debugging Print CSS
Chrome DevTools Print Emulation
Chrome DevTools can emulate print media:
- Open DevTools (F12)
- Open the Rendering panel (Cmd/Ctrl + Shift + P → "Show Rendering")
- Set "Emulate CSS media type" to "print"
This shows you how your print styles look without actually printing. However, it doesn't show page breaks or margins — for those, use "File → Print → Preview."
Common Debugging Tips
-
Background colors don't print by default. Add
print-color-adjust: exact;(or-webkit-print-color-adjust: exact;) to force backgrounds in print. -
Margins are cumulative. The
@page marginand body margin both apply. Set body margin to 0 when using@pagemargins. -
Images may not load. When rendering from HTML strings (not URLs), images with relative paths often fail. Use absolute URLs or base64-encoded images.
-
Viewport width matters. Puppeteer renders at 800px viewport width by default, which may trigger mobile breakpoints. Set viewport width explicitly if needed.
Conclusion
Print CSS is a powerful, underused part of the web platform. For anyone generating PDFs from HTML, mastering @page rules, break properties, and margin boxes is essential. These aren't obscure features — they're the difference between a professional document and one that looks like a screenshot pasted into a PDF.
The CSS Paged Media specification gives you fine-grained control over print layout. The main challenge is browser support — but if you choose the right PDF engine for your needs, you can use these features today.
Building PDF templates with HTML/CSS? PDF-API.io renders your templates with full CSS support and handles the tricky parts — page breaks, fonts, and margins — automatically. Start for free.