Log In Sign Up

Blog

Print CSS basics in 10 minutes

Updated

When you ask a browser to render a page for paper or PDF, it stops being a continuous canvas and starts being a sequence of fixed-size pages. That single change is the source of every print-CSS quirk you’ve ever hit. The good news: the CSS you need to control it is small, well-supported in 2026, and learnable in about ten minutes.

This is the version of the post we wish we’d had when we first built an HTML-to-PDF service. Originally written in 2019, refreshed for 2026 with the syntax that’s now stable across Chrome, Edge, Safari, and Firefox.

The @media print rule

Everything starts here. Wrap any rule you want to apply only when printing in @media print:

@media print {
  .site-nav,
  .cookie-banner,
  .chat-widget {
    display: none;
  }

  body {
    font-size: 11pt;
    color: #000;
    background: #fff;
  }
}

Three things to notice:

  1. Use pt for type sizes, not px. Print resolution and screen resolution aren’t the same thing; pt is the unit print engineers expect.
  2. Force black text on white background. PDFs and paper are read in different conditions than screens. Reset your dark-mode-friendly defaults.
  3. Hide chrome. Anything that’s interactive on screen, nav, banners, chat widgets, share buttons, should disappear in print.

If you want a rule to apply to both print and screen but with different values, use the cascade naturally:

.byline { color: #555; }

@media print {
  .byline { color: #000; }
}

There’s no “screen-only” media query you need to remember. The default is screen.

Page break properties

The most common print bug is content getting awkwardly chopped between pages. CSS has three properties for this:

.section-heading {
  break-before: page;        /* start this element on a new page */
}

.invoice-footer {
  break-after: page;         /* force a new page after this element */
}

.do-not-split {
  break-inside: avoid;       /* don't split this element across pages */
}

The older page-break-before, page-break-after, and page-break-inside still work, but the unprefixed break-* properties are part of the modern CSS Fragmentation spec and have been the recommended form since 2020.

Practical recipe for a multi-page report:

@media print {
  h1, h2 { break-after: avoid; }    /* don't orphan a heading at the bottom */
  table  { break-inside: avoid; }   /* keep tables together where possible */
  figure { break-inside: avoid; }   /* don't split a chart from its caption */
  .chapter { break-before: page; }  /* each chapter starts a new page */
}

Tip: repeat table headings

Long tables that span multiple pages are unreadable if the header row only appears on page one. Browsers will repeat <thead> automatically if you use the right markup:

<table>
  <thead>
    <tr><th>SKU</th><th>Description</th><th>Price</th></tr>
  </thead>
  <tbody>
    <tr><td>...</td><td>...</td><td>...</td></tr>
  </tbody>
</table>

No CSS needed, browsers do this for free as long as you’ve actually used <thead> and not just a <tr> with bold cells. The headless rendering engines (Chrome, WebKit) all honor this.

Tip: adding or removing content for print

You’ll often want to add something for print that doesn’t appear on screen, the most common case is expanding link URLs:

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

  a[href^="#"]::after,
  a[href^="javascript:"]::after {
    content: "";    /* don't expand anchor links or JS handlers */
  }
}

The reader of a printed page can’t click a link, so showing the destination URL after the anchor text is genuinely useful. Skip it for in-page anchor links and JavaScript handlers, which would just be noise.

Tip: use Chrome DevTools to emulate print

You don’t need to print or generate a PDF every time you tweak a rule. In Chrome DevTools:

  1. Open the Rendering panel (Command Menu → “Show Rendering”)
  2. Find Emulate CSS media type
  3. Set it to print

Your @media print rules now apply live in the viewport. Same trick exists in Firefox (Inspector → Print Preview button) and Safari (Develop → Enter Responsive Design Mode → media type selector).

This is the single biggest productivity boost for print-CSS work. We use it constantly when tuning Converterer’s rendering pipeline.

Advanced: orphans and widows

An orphan is a single line stranded at the bottom of a page; a widow is a single line stranded at the top of the next page. Both look bad and both are controlled by CSS:

@media print {
  p {
    orphans: 3;    /* require at least 3 lines at the bottom of a page */
    widows: 3;     /* require at least 3 lines at the top of the next */
  }
}

Three is a sensible default. Higher values produce more whitespace at the bottom of pages but no awkward line splits. As of 2026, all four major rendering engines honor orphans and widows correctly, this used to be a “WebKit only” thing.

Advanced: the @page rule

@page controls the page itself, its size, margins, and the headers and footers that appear in margin boxes:

@page {
  size: A4;                          /* or "letter", "210mm 297mm", "8.5in 11in" */
  margin: 25mm 20mm;
}

@page :first {
  margin-top: 50mm;                  /* extra space on the title page */
}

@page :left  { margin-left: 30mm; margin-right: 20mm; }
@page :right { margin-left: 20mm; margin-right: 30mm; }

Margin boxes let you put content in the page margins, typically headers, footers, page numbers:

@page {
  margin: 25mm 20mm 25mm 20mm;

  @top-center {
    content: "Quarterly Report, Q1 2026";
    font-size: 9pt;
    color: #666;
  }

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

The available margin boxes are @top-left, @top-center, @top-right, @bottom-left, @bottom-center, @bottom-right, plus -corner variants. Chrome added full support for these in 2023; Safari followed in 2024; Firefox is partial. If you’re rendering with headless Chrome or any Chrome-based PDF service (Converterer included), you can rely on them.

counter(page) and counter(pages) are built in. You don’t have to declare them.

What changed in 2026

A few things worth knowing if you’re updating older print stylesheets:

  • color-adjust is now print-color-adjust. The old name is deprecated. Use print-color-adjust: exact if you want background colors and images to print rather than being stripped:
    .receipt-header {
      background: #f0f0f0;
      print-color-adjust: exact;
      -webkit-print-color-adjust: exact;
    }
  • Container Queries don’t apply to print pagination, but they do apply to layout within a printed page. If you’re conditionally rendering layouts based on container size, those rules carry over.
  • Variable fonts work fine in PDFs as of all current rendering engines. No special handling needed beyond the usual font-display: swap for screen display.

Summary

Print CSS in 2026 is roughly:

@media print {
  /* hide the parts of the page that aren't content */
  .site-nav, .footer-cta, .chat-widget { display: none; }

  /* set print-friendly type and color */
  body { font-size: 11pt; color: #000; background: #fff; }

  /* control page breaks */
  h1, h2 { break-after: avoid; }
  table, figure { break-inside: avoid; }

  /* expand link URLs */
  a[href]::after { content: " (" attr(href) ")"; font-size: 90%; }

  /* avoid stranded lines */
  p { orphans: 3; widows: 3; }
}

@page {
  size: A4;
  margin: 25mm 20mm;
  @bottom-right { content: counter(page); font-size: 9pt; }
}

That’s most of what you need. The rest is taste.

If you want to go further, handling more complex paginated layouts, multi-column print, or print-specific cross-references, the paged.js project bridges what’s missing in browser engines today. We cover when it’s worth reaching for in Modern HTML to PDF conversion.

If you’d rather skip building a PDF rendering pipeline altogether, our website-capture API does exactly this, you give it a URL, it gives you back a pixel-perfect PDF, and you don’t have to maintain a headless Chrome cluster.