Get started

CSS print styles: the complete guide for PDF generation

Master @media print, @page margins, page breaks, widows/orphans, and table headers. Interactive demos show each CSS property in action.

benoitdedMarch 14, 202611 min read

CSS print styles are CSS rules that only apply when a page is printed or exported to PDF. They control page margins, page breaks, typography, and element visibility, and they are the foundation of every HTML-to-PDF pipeline. Whether you use Playwright, Puppeteer, or an API like PDF4.dev, the browser applies @media print styles before generating the PDF.

This guide covers every print CSS property with interactive demos you can manipulate to see the effect in real time.

What does @media print do?

@media print is a CSS media query that targets print output. Styles inside this block only apply when the document is printed or rendered to PDF. The browser ignores them on screen.

The most common use is hiding non-essential UI elements: navigation bars, sidebars, cookie banners, and footers. Toggle between screen and print below to see what changes.

@media screen
MyApp
Menu
Dashboard
Reports
Settings
Quarterly report
Revenue grew 23% to $4.2M in Q3. The cloud migration reduced infrastructure costs by 12%, improving margins. Enterprise contracts drove most of the growth.
Read more →
@media print { nav, .sidebar, .footer { display: none; } a::after { content: " (" attr(href) ")"; } }

Three patterns appear in nearly every print stylesheet:

  1. Hide chrome: navigation, sidebars, ads, cookie banners get display: none
  2. Simplify colors: backgrounds become white, text becomes black, links lose their color
  3. Expand links: a::after { content: " (" attr(href) ")"; } prints the URL next to each link, so readers can follow references on paper
@media print {
  nav, .sidebar, footer, .cookie-banner {
    display: none;
  }
 
  body {
    background: white;
    color: black;
    font-size: 12pt;
    line-height: 1.5;
  }
 
  a {
    color: black;
    text-decoration: underline;
  }
 
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 80%;
    color: #666;
  }
}

You can test print styles without printing. In Chrome DevTools, open the Rendering panel and set "Emulate CSS media type" to "print". Firefox has a print toggle in the Style Editor.

How to set PDF page margins with CSS

The @page at-rule defines properties that apply to printed pages. Its most important property is margin, which sets the non-printable area around the content.

Use the sliders below to see how margin values affect the printable area.

Margin editor
Presets
top20mm
right15mm
bottom20mm
left15mm
Report title
Content flows within the margins. Adjust the sliders to see how margin values change the printable area. Larger margins reduce the available space for content.
Section two
The margin values are passed to the browser print engine via the @page CSS at-rule.
1/1
@page { margin: 20mm 15mm 20mm 15mm; }

Margin best practices for PDF

ScenarioRecommended marginsWhy
Standard document20mm 15mmComfortable reading, safe for all printers
Full-bleed design0Edge-to-edge content (posters, certificates)
Bound document20mm 10mm 20mm 25mmExtra left margin for binding
Legal document25mm 20mmSpace for signatures and stamps

Use absolute units (mm, cm, in) for print margins, not relative units like em or %. Absolute units produce consistent results across different PDF engines and printers. The CSS @page specification supports all absolute CSS length units.

/* Named page types with different margins */
@page {
  margin: 20mm 15mm;
}
 
@page :first {
  margin-top: 40mm; /* extra space for letterhead on page 1 */
}
 
@page :left {
  margin-left: 25mm; /* binding margin on left pages */
}
 
@page :right {
  margin-right: 25mm; /* binding margin on right pages */
}

How to set page size and orientation with CSS

The size property inside @page controls the dimensions and orientation of printed pages. It accepts standard page names (A4, letter), explicit dimensions, or the landscape/portrait keywords.

/* Standard page sizes */
@page { size: A4; }           /* 210mm x 297mm */
@page { size: A4 landscape; } /* 297mm x 210mm */
@page { size: letter; }       /* 8.5in x 11in */
 
/* Custom dimensions */
@page { size: 148mm 210mm; }  /* A5 */
@page { size: 100mm 100mm; }  /* Square label */
Size keywordDimensionsUse case
A4210 x 297mmStandard international documents
A4 landscape297 x 210mmWide tables, presentations
letter8.5 x 11inUS standard documents
A5148 x 210mmBooklets, flyers
Custom WxHAny dimensionsLabels, tickets, badges

The size property is supported in Chromium-based browsers and Playwright. Firefox ignores it and uses the user's print dialog settings instead. When generating PDFs with Playwright or PDF4.dev, size in CSS is overridden by the API's format parameter if both are specified.

For PDF generation via API, prefer passing page size through the format parameter rather than CSS @page { size }. This keeps your HTML portable across different output formats.

How to control page breaks in CSS

Page breaks determine where content splits across pages. CSS provides three properties for this: break-before, break-after, and break-inside. These replace the older page-break-* properties, which are now legacy aliases.

Toggle between break rules below to see how content distributes across pages.

Page break playground
Break rule
Introduction
This section introduces the quarterly report with key highlights and executive summary.
Revenue breakdown
Q3 revenue reached $4.2M, a 23% increase year-over-year driven by enterprise contracts.
1/2
Expenses
Operating expenses totaled $2.8M. Infrastructure costs decreased 12% after the cloud migration.
Forecast
Q4 projections indicate $5.1M revenue with continued margin improvement across all segments.
2/2
/* No break rules applied */

The three break properties

PropertyWhat it doesCommon value
break-beforeForces or prevents a break before an elementpage, avoid
break-afterForces or prevents a break after an elementpage, avoid
break-insidePrevents a break inside an elementavoid

Practical page break patterns

/* Force each chapter to start on a new page */
h2 {
  break-before: page;
}
 
/* Keep figures and tables together (never split across pages) */
figure, table, .card {
  break-inside: avoid;
}
 
/* Prevent a heading from being the last thing on a page */
h2, h3, h4 {
  break-after: avoid;
}
 
/* Cover page: force a break after it */
.cover-page {
  break-after: page;
}

The legacy page-break-before, page-break-after, and page-break-inside properties still work in all browsers. They are treated as aliases for the modern break-* properties. Use the modern syntax in new projects.

What break-inside: avoid actually means

break-inside: avoid tells the browser "do not split this element across a page boundary." This is essential for elements that lose meaning when broken: tables, charts, cards, address blocks, code snippets.

If the element is taller than a full page, the browser ignores the rule and breaks it anyway. There is no way to prevent this with CSS alone.

/* Protect all critical blocks from splitting */
.invoice-line-item,
.testimonial-card,
.code-block,
.signature-block {
  break-inside: avoid;
}

How do CSS widows and orphans work?

Widows and orphans are typographic terms borrowed from print design. An orphan is a line stranded at the bottom of a page, and a widow is a line stranded at the top of the next page. Both look awkward and reduce readability.

CSS lets you set minimum line counts on each side of a page break. Drag the sliders below to see how different values redistribute text.

Widows and orphans
orphans1
Minimum lines at the bottom of the current page
widows1
Minimum lines at the top of the next page
The quarterly financial report shows a clear upward
trend across all business segments. Revenue increased
by 23% compared to the same period last year,
driven primarily by enterprise contract renewals.
Operating margins improved as infrastructure costs
1/2
page break
were reduced following the cloud migration initiative.
Customer acquisition cost dropped 15% while lifetime
value increased, indicating stronger product-market fit.
2/2
p { orphans: 1; widows: 1; }

The default value for both widows and orphans is 2 in most browsers. Setting them to 3 produces noticeably better typographic results in long documents like reports, contracts, and articles.

p {
  orphans: 3; /* at least 3 lines before a page break */
  widows: 3;  /* at least 3 lines after a page break */
}
 
/* For critical blocks, be more aggressive */
.legal-clause {
  orphans: 4;
  widows: 4;
}

Widows and orphans are hints, not hard rules. If the browser cannot satisfy the constraint without creating an empty page, it may ignore the value. Chromium-based engines (Chrome, Playwright, PDF4.dev) handle these properties reliably for body text.

How to repeat table headers across printed pages

When a table spans multiple pages, readers on page 2 lose context because column headers are only on page 1. The fix is semantic HTML: wrap headers in <thead>. Chromium-based browsers automatically repeat <thead> content on every printed page.

Toggle between <thead> and no <thead> below to see the difference.

Table header repetition
ProductQ1Q2Q3
Cloud Hosting$12,400$14,200$16,800
API Gateway$8,300$9,100$11,500
Storage$5,600$6,200$7,100
CDN$3,200$3,800$4,500
Auth Service$2,100$2,400$2,900
1/2
page break
ProductQ1Q2Q3
Analytics$1,800$2,100$2,600
Email$1,400$1,600$1,900
Webhooks$900$1,100$1,400
2/2
<table> <thead> <tr><th>Product</th><th>Q1</th>...</tr> </thead> <tbody><!-- rows --></tbody> </table>

This behavior comes from the CSS 2.1 specification and is implemented in Chrome, Firefox, and all Chromium-based PDF engines including Playwright. The same technique works for <tfoot>, which repeats at the bottom of every page.

<table>
  <thead>
    <tr>
      <th>Product</th>
      <th>Q1</th>
      <th>Q2</th>
      <th>Q3</th>
    </tr>
  </thead>
  <tbody>
    <!-- data rows -->
  </tbody>
  <tfoot>
    <tr>
      <td colspan="4">Report generated by PDF4.dev</td>
    </tr>
  </tfoot>
</table>

For extra safety, add display: table-header-group explicitly in your print stylesheet:

@media print {
  thead {
    display: table-header-group;
  }
  tfoot {
    display: table-footer-group;
  }
}

PDF4.dev uses this exact <thead>/<tfoot> pattern for its component system. When you attach a header or footer component to a template, it gets rendered inside <thead> and <tfoot> so it repeats on every page automatically.

How does print-color-adjust work?

Browsers strip background colors and images in print mode by default to save ink. The print-color-adjust property overrides this behavior. Set it to exact to force the browser to render backgrounds, gradients, and images as they appear on screen.

@media print {
  body {
    -webkit-print-color-adjust: exact; /* Safari, older Chrome */
    print-color-adjust: exact;         /* standard */
  }
}
ValueBehaviorWhen to use
economy (default)Browser may strip backgrounds to save inkText-heavy documents, contracts
exactAll backgrounds, gradients, and images preservedInvoices, certificates, branded reports

The -webkit- prefix is still required for Safari. Chromium dropped the prefix in version 98, but including both ensures compatibility. When generating PDFs with Playwright, pass printBackground: true in the API call to achieve the same result.

Common CSS print pitfalls

Using viewport units for layout

vw and vh refer to the screen viewport, not the printed page. A width: 100vw element may overflow or underflow the page. Use %, mm, or cm for print layouts instead.

Not testing multi-page output

A stylesheet that looks fine on one page may break on page 2. Always test with enough content to trigger at least 3 pages. Apply break-inside: avoid on cards, figures, and tables to catch splitting issues early.

Forgetting @page interacts with API margins

When using a PDF generation API that accepts margin parameters (Playwright, PDF4.dev), the API margins override @page { margin }. If you set margins in both places, the API wins. Pick one source of truth: either CSS @page or the API parameter.

Browser and PDF engine compatibility

Not every CSS print property works everywhere. This table shows support across the engines that matter for PDF generation.

PropertyChrome / PlaywrightFirefoxSafariwkhtmltopdf
@media printYesYesYesYes
@page { margin }YesYesYesYes
@page { size }YesNo (ignored)NoPartial
break-before/afterYesYesYesLegacy only
break-inside: avoidYesYesPartialYes
orphans / widowsYesYesNoNo
print-color-adjustYesYesPrefix onlyN/A
<thead> repetitionYesYesYesYes

Chromium-based engines (Chrome, Playwright, PDF4.dev) have the most complete support. If you target Chromium for PDF generation, every property in this guide works reliably.

From CSS to production PDFs

CSS print styles work great for one-off printing from a browser. For automated PDF generation at scale, you need a rendering engine that applies these styles programmatically.

PDF4.dev uses Playwright (headless Chromium) under the hood. Every CSS property covered in this guide, from @page margins to break-inside: avoid to <thead> repetition, works exactly the same in generated PDFs as it does in Chrome's print preview.

curl -X POST https://pdf4.dev/api/v1/render \
  -H "Authorization: Bearer p4_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<style>@page { margin: 20mm } h2 { break-before: page } thead { display: table-header-group }</style><h1>Report</h1>...",
    "format": { "preset": "a4" }
  }'

The difference: with PDF4.dev, you skip browser management, Docker images, cold starts, and concurrency issues. One API call, one PDF. The CSS is the same either way.

HTML to PDFTry it free

Free tools mentioned:

Html To PdfTry it free

Start generating PDFs

Build PDF templates with a visual editor. Render them via API from any language in ~300ms.