May 5, 2026

CSS Print Stylesheet Guide: @page, @media print & PDF Output

CSSPDF generationprint stylesheetlayouttutorial

Generating PDF from HTML with CSS sounds straightforward — until backgrounds disappear, page breaks land in wrong places, table headers vanish, and @page margins seem to be ignored. This guide collects every Chromium print CSS technique you need, with copy-paste snippets for each problem.

Whether you use FUNBREW PDF, Puppeteer, or Playwright, the CSS engine is the same: Chromium's print pipeline. The rules here apply to all three.

For CSS problems specific to @page layout and page breaks, see HTML to PDF CSS Fixes. If you are embedding Japanese or CJK fonts, see the Japanese Font Guide. For troubleshooting blank pages and broken layouts, see the HTML to PDF Troubleshooting guide.

The Two CSS Contexts: @media print vs @page

Understanding which CSS rule applies where is the first step.

@media print @page
Purpose Scopes styles to print/PDF context Configures the physical page
What it controls Fonts, colors, visibility, layout Size, orientation, margins
Supports CSS vars Yes No
Nested in the other Can contain @page Standalone or inside @media print
Example @media print { .nav { display: none } } @page { size: A4; margin: 20mm }

Always structure your print stylesheet in this order:

/* 1. Scope everything in @media print */
@media print {

  /* 2. Configure the physical page inside @page */
  @page {
    size: A4 portrait;
    margin: 20mm 15mm 25mm 15mm; /* top right bottom left */
  }

  /* 3. Override screen styles for print */
  body {
    font-size: 11pt;
    color: #000;
  }

  /* 4. Hide elements not needed in print */
  .no-print,
  nav,
  .sidebar {
    display: none !important;
  }
}

@page: Size, Orientation, and Margins

Named page sizes

@page { size: A4 portrait; }     /* 210mm × 297mm */
@page { size: A4 landscape; }    /* 297mm × 210mm */
@page { size: Letter portrait; } /* 8.5in × 11in */
@page { size: Legal; }           /* 8.5in × 14in */
@page { size: A3 landscape; }    /* 420mm × 297mm */

Custom dimensions

@page {
  size: 210mm 297mm; /* explicit width height */
  margin: 20mm;
}

Per-page margin control

@page          { margin: 20mm 15mm; }         /* all pages */
@page :first   { margin-top: 40mm; }          /* cover page */
@page :left    { margin-left: 25mm; }         /* even pages (binding) */
@page :right   { margin-right: 25mm; }        /* odd pages (binding) */

Note: CSS custom properties (var()) do not work inside @page rules. Use hard-coded values.

Page Breaks: break-before, break-after, break-inside

Use both the modern break-* properties and the legacy page-break-* fallbacks for maximum compatibility.

Force a page break before a section

.chapter,
.report-section,
h1 {
  break-before: page;
  page-break-before: always; /* legacy fallback */
}

Prevent a break inside a block

.card,
.figure,
.keep-together {
  break-inside: avoid;
  page-break-inside: avoid; /* legacy fallback */
}

Force a break after a cover page

.cover {
  break-after: page;
  page-break-after: always;
  height: 100vh; /* fills the first page */
}

Flex and grid layout gotcha

break-inside: avoid applied directly to flex or grid children is often ignored. Wrap each child in a block <div>:

<!-- Works -->
<div class="grid-wrapper">
  <div class="card-wrapper"> <!-- break-inside: avoid here -->
    <div class="card">...</div>
  </div>
</div>
.card-wrapper {
  break-inside: avoid;
  page-break-inside: avoid;
}

Background Colors and Images

Global fix for all elements

@media print {
  * {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

Per-element fix

.colored-header,
.status-badge,
.chart-background {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

Background images

.banner {
  background-image: url('/img/logo.png');
  background-size: cover;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

When using FUNBREW PDF, pass printBackground: true in the API options to enable this globally without CSS:

{
  "options": {
    "printBackground": true
  }
}

Tables: Repeating Headers, Avoiding Orphan Rows

Repeat table header on every page

thead {
  display: table-header-group;
}

tfoot {
  display: table-footer-group;
}

tbody {
  display: table-row-group;
}
<table>
  <thead>
    <tr>
      <th>Name</th><th>Amount</th><th>Date</th>
    </tr>
  </thead>
  <tbody>
    <!-- rows repeat across pages -->
  </tbody>
</table>

Prevent row orphans (rows split across pages)

tr {
  break-inside: avoid;
  page-break-inside: avoid;
}

Fixed table layout for consistent column widths

table {
  table-layout: fixed;
  width: 100%;
  border-collapse: collapse;
}

th, td {
  word-wrap: break-word;
  overflow-wrap: break-word;
}

Fonts: Loading and Embedding

System font stack (no external requests)

body {
  font-family: -apple-system, 'Segoe UI', Arial, sans-serif;
}

Web font with @font-face (self-hosted, no CDN)

@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: block; /* wait for font to load */
}

body {
  font-family: 'Inter', sans-serif;
}

Base64-embedded font (no HTTP requests at render time)

@font-face {
  font-family: 'MyFont';
  src: url('data:font/woff2;base64,AAABAA...') format('woff2');
}

Embedding fonts as Base64 eliminates CDN dependencies and is the most reliable approach for PDF generation. See the Japanese Font Guide for embedding CJK fonts.

Google Fonts via CDN (requires network at render time)

<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=block" rel="stylesheet">

When using FUNBREW PDF, pass waitForNetworkIdle: true to ensure fonts finish loading before the PDF is captured:

{
  "options": {
    "waitForNetworkIdle": true
  }
}

Page Numbers and Running Headers/Footers

CSS counter-based page numbers (no JavaScript)

@page {
  @bottom-center {
    content: "Page " counter(page) " of " counter(pages);
    font-size: 9pt;
    color: #888;
  }
}

Chromium note: The CSS Paged Media @bottom-center content with counter(pages) requires a Chromium version that supports running headers. Use the FUNBREW PDF headerTemplate/footerTemplate API option for more reliable control.

FUNBREW PDF headerTemplate / footerTemplate

{
  "options": {
    "displayHeaderFooter": true,
    "headerTemplate": "<div style='font-size:9pt; color:#888; padding:0 15mm; width:100%; display:flex; justify-content:space-between;'><span>My Report</span><span class='date'></span></div>",
    "footerTemplate": "<div style='font-size:9pt; color:#888; padding:0 15mm; width:100%; text-align:center;'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></div>",
    "margin": { "top": "25mm", "bottom": "20mm" }
  }
}

Built-in Chromium classes for use in template strings:

Class Outputs
<span class='pageNumber'> Current page number
<span class='totalPages'> Total page count
<span class='date'> Current formatted date
<span class='title'> Document title
<span class='url'> Document URL

Hiding Elements in Print

Common elements to hide

@media print {
  /* Navigation and chrome */
  nav,
  header.site-header,
  footer.site-footer,
  .sidebar,
  .breadcrumb,
  .pagination,

  /* Interactive elements */
  button,
  input,
  select,
  textarea,
  form,

  /* Utility classes */
  .no-print,
  .screen-only {
    display: none !important;
  }
}

Show print-only elements

.print-only {
  display: none;
}

@media print {
  .print-only {
    display: block;
  }
}

Example: show full URL after links

@media print {
  a[href]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    color: #555;
  }

  /* Exclude internal anchors */
  a[href^="#"]::after {
    content: "";
  }
}

Layout Patterns for PDF

Full-bleed cover page

@page :first {
  margin: 0;
}

.cover-page {
  width: 210mm;
  height: 297mm;
  padding: 40mm 30mm;
  background: #1e3a5f;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  break-after: page;
}

Two-column layout with print-safe CSS

.two-column {
  column-count: 2;
  column-gap: 10mm;
  column-rule: 1px solid #e2e8f0;
}

/* Prevent headings from splitting across columns */
h2, h3 {
  break-after: avoid;
  column-span: none;
}

Invoice / data table layout

.invoice-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 10pt;
}

.invoice-table th {
  background: #1e3a5f;
  color: #fff;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
  padding: 3mm 4mm;
  text-align: left;
}

.invoice-table td {
  padding: 2.5mm 4mm;
  border-bottom: 0.5px solid #e2e8f0;
}

.invoice-table tr:nth-child(even) td {
  background: #f8fafc;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

Using the CSS with FUNBREW PDF API

Combine all the techniques above and send the final HTML to the API:

import fs from 'fs';

const html = fs.readFileSync('report.html', 'utf-8');

const response = await fetch('https://pdf.funbrew.cloud/api/v1/generate', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.FUNBREW_PDF_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    html,
    options: {
      format: 'A4',
      printBackground: true,    // enable background colors and images
      waitForNetworkIdle: true, // wait for web fonts and CDN resources
      displayHeaderFooter: true,
      headerTemplate: '<div style="font-size:9pt;padding:0 15mm;width:100%;color:#888;">My Document</div>',
      footerTemplate: '<div style="font-size:9pt;padding:0 15mm;width:100%;text-align:right;color:#888;">Page <span class="pageNumber"></span></div>',
      margin: { top: '25mm', right: '15mm', bottom: '20mm', left: '15mm' },
    },
  }),
});

const pdf = Buffer.from(await response.arrayBuffer());
fs.writeFileSync('output.pdf', pdf);
import requests, os

with open('report.html', 'r') as f:
    html = f.read()

response = requests.post(
    'https://pdf.funbrew.cloud/api/v1/generate',
    headers={'Authorization': f'Bearer {os.environ["FUNBREW_PDF_API_KEY"]}'},
    json={
        'html': html,
        'options': {
            'format': 'A4',
            'printBackground': True,
            'waitForNetworkIdle': True,
            'displayHeaderFooter': True,
            'headerTemplate': '<div style="font-size:9pt;padding:0 15mm;width:100%;color:#888;">My Document</div>',
            'footerTemplate': '<div style="font-size:9pt;padding:0 15mm;width:100%;text-align:right;color:#888;">Page <span class="pageNumber"></span></div>',
            'margin': {'top': '25mm', 'right': '15mm', 'bottom': '20mm', 'left': '15mm'},
        },
    },
)
response.raise_for_status()
with open('output.pdf', 'wb') as f:
    f.write(response.content)

Test your CSS in the FUNBREW PDF Playground before integrating into your codebase.

Quick-Reference: 25 CSS Print Snippets

Problem Fix
Background color missing print-color-adjust: exact
Background image missing print-color-adjust: exact on element
Page break before section break-before: page; page-break-before: always
Prevent break inside block break-inside: avoid; page-break-inside: avoid
Page break after cover break-after: page; page-break-after: always
Table header every page thead { display: table-header-group }
Table footer every page tfoot { display: table-footer-group }
Row not split tr { break-inside: avoid }
Fixed column widths table { table-layout: fixed }
Custom page size @page { size: A4 landscape }
Custom margins @page { margin: 20mm 15mm }
First-page margin @page :first { margin-top: 40mm }
Hide nav in PDF @media print { nav { display: none } }
Font loads reliably @font-face with local WOFF2
Font via CDN Add waitForNetworkIdle: true in API options
Page number <span class='pageNumber'> in footerTemplate
Total pages <span class='totalPages'> in footerTemplate
Full-bleed cover @page :first { margin: 0 }
Two-column layout column-count: 2; column-gap: 10mm
Links print URL a[href]::after { content: attr(href) }
Flex break fix Wrap children in block div
Enable bg globally printBackground: true in API options
Wait for fonts waitForNetworkIdle: true in API options
Header template displayHeaderFooter: true + headerTemplate
Footer template displayHeaderFooter: true + footerTemplate

Related Guides

Powered by FUNBREW PDF