How to Add a Print Stylesheet to a Website So Pages Print Correctly

Most websites look great on screen and terrible on paper. Menus eat half a page, a sidebar pushes the article into a narrow column, dark backgrounds burn through ink, and links become useless because the URL is invisible. A CSS print stylesheet fixes all of that in about 60 lines of code.

This tutorial gives you a complete, copy-paste print stylesheet, explains every block so you can adapt it, shows how to preview the result in Chrome DevTools without wasting paper, and ends with a WordPress-specific section on exactly where to enqueue the file.

What a CSS print stylesheet actually does

A print stylesheet is a set of CSS rules that the browser applies only when the page is sent to a printer or saved as a PDF. It uses the print media type, which browsers activate during printing and during print preview. CSS-Tricks Finally Gets A Print Stylesheet covers this in more depth.

Two things matter here:

  • Print styles are additive. Your screen CSS still applies unless you override it. That is why every practical print stylesheet starts by resetting colors, widths and floats.
  • Saving as PDF from the browser uses the same print stylesheet. Fixing printing also fixes “Save as PDF”, which is how most people actually “print” today.
web design

Two ways to add print CSS (and which one to pick)

Method Code Best for
@media print block inside your main CSS @media print { ... } Small to medium sites, one HTTP request, easiest to maintain
Separate file loaded with a media attribute <link rel="stylesheet" href="/css/print.css" media="print"> Large sites, CMS themes, keeping print rules isolated from screen rules
Both Separate file that contains one @media print wrapper Safest option: the rules stay scoped even if the file gets concatenated by a build tool or a cache plugin

Recommendation: create print.css, wrap its content in @media print { }, and load it with media="print". Browsers download it at low priority, so it does not block your screen rendering, and the wrapper protects you from build pipelines that merge stylesheets.

web design

The copy-paste CSS print stylesheet

Drop this into print.css. It is intentionally generic: adjust the selectors in the “hide” block to match your markup and you are done.

@media print {

  /* ---------- 1. Page geometry ---------- */
  @page {
    size: A4 portrait;
    margin: 15mm 12mm;
  }

  /* ---------- 2. Readable black on white ---------- */
  html,
  body {
    background: #fff !important;
    color: #000 !important;
    font-size: 12pt;
    line-height: 1.45;
    width: auto !important;
    margin: 0 !important;
    padding: 0 !important;
    float: none !important;
  }

  body {
    font-family: Georgia, "Times New Roman", serif;
  }

  * {
    background: transparent !important;
    color: #000 !important;
    box-shadow: none !important;
    text-shadow: none !important;
  }

  /* ---------- 3. Hide screen-only interface ---------- */
  nav,
  aside,
  header nav,
  .site-header,
  .site-navigation,
  .main-navigation,
  .menu-toggle,
  .sidebar,
  .widget-area,
  .breadcrumbs,
  .site-footer,
  .comments-area,
  .comment-respond,
  .share-buttons,
  .social-links,
  .related-posts,
  .newsletter,
  .cookie-banner,
  .back-to-top,
  .ad,
  .ads,
  .advertisement,
  [id*="google_ads"],
  [class*="adsbygoogle"],
  iframe,
  video,
  audio,
  form,
  button,
  .no-print {
    display: none !important;
  }

  /* Utility: force something to appear only on paper */
  .print-only {
    display: block !important;
  }

  /* ---------- 4. Give the content the full page ---------- */
  .content,
  .site-content,
  .entry-content,
  main,
  article {
    width: 100% !important;
    max-width: 100% !important;
    margin: 0 !important;
    padding: 0 !important;
    float: none !important;
    display: block !important;
  }

  /* Kill sticky and fixed elements, they repeat on every page */
  * {
    position: static !important;
  }

  /* ---------- 5. Links: show the destination ---------- */
  a,
  a:visited {
    text-decoration: underline;
  }

  a[href^="http"]:after {
    content: " (" attr(href) ")";
    font-size: 90%;
    word-wrap: break-word;
  }

  /* Do not print URLs that are useless on paper */
  a[href^="#"]:after,
  a[href^="javascript:"]:after,
  a[href^="mailto:"]:after,
  a[href^="tel:"]:after,
  .no-url:after {
    content: "";
  }

  abbr[title]:after {
    content: " (" attr(title) ")";
  }

  /* ---------- 6. Page breaks ---------- */
  h1, h2, h3, h4, h5, h6 {
    break-after: avoid;
    break-inside: avoid;
    page-break-after: avoid;   /* legacy fallback */
  }

  p, blockquote, li {
    orphans: 3;
    widows: 3;
  }

  img,
  figure,
  table,
  pre,
  blockquote {
    break-inside: avoid;
    page-break-inside: avoid;  /* legacy fallback */
  }

  .page-break {
    break-before: page;
    page-break-before: always;
  }

  /* ---------- 7. Media and tables ---------- */
  img {
    max-width: 100% !important;
    height: auto !important;
  }

  figure {
    margin: 0 0 1em;
  }

  figcaption {
    font-size: 10pt;
    font-style: italic;
  }

  table {
    width: 100% !important;
    border-collapse: collapse;
  }

  th, td {
    border: 1px solid #000;
    padding: 4pt 6pt;
  }

  thead {
    display: table-header-group; /* repeats headers on each page */
  }

  tr {
    break-inside: avoid;
  }

  pre, code {
    font-family: "Courier New", monospace;
    font-size: 10pt;
    white-space: pre-wrap;
    word-wrap: break-word;
    border: 1px solid #999;
  }

  blockquote {
    border-left: 3pt solid #000;
    padding-left: 8pt;
  }
}

Rule by rule: what each block is doing

1. @page controls the paper, not the HTML

The @page at-rule sets the physical sheet: size and margins. Use A4 for most of the world, letter for North America, or omit size entirely and let the user choose in the print dialog. Margins in mm or in are more predictable than pixels because paper is a physical medium.

You can also target specific pages:

@page :first {
  margin-top: 25mm;
}

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

Browser support for headers, footers and page counters inside @page is still limited in Chrome, Firefox and Safari, so do not rely on @bottom-center { content: counter(page) } for anything critical. The browser adds its own header and footer, and the user can toggle those in the print dialog.

2. Black text on white paper

Dark themes, colored cards and gradient hero sections cost ink and reduce contrast on paper. Resetting background: transparent and color: #000 with !important is blunt but effective.

Also switch to point units for print. 12pt body text with 10pt captions is a safe, readable baseline. Pixels work, but points map directly to what printers understand. For the wider picture, see CSS print page styling.

If you have a chart, a logo or a color-coded badge that must keep its color, opt back in:

.chart,
.brand-logo {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
  background: initial !important;
}

3. Hiding navigation, sidebars and ads

This is the block you must customize. Open your page source, list every element that is pure interface, and add its selector. A reliable trick is to add a generic .no-print class to your templates so future components can opt out of printing without touching the stylesheet.

The mirror utility, .print-only, is useful for paper-only content, for example a line with the canonical URL and the date:

<p class="print-only" style="display:none">Printed from expressjs.org</p>

4. Reclaim the full width

Once the sidebar is gone, the article container often keeps a max-width: 720px or a grid column, which leaves a huge empty area. Forcing width: 100% and float: none on the content wrappers gives you the whole sheet. Flexbox and grid layouts usually survive printing, but if a multi-column layout breaks across pages badly, set display: block on the container.

The position: static !important rule is important: fixed headers, sticky sidebars and floating chat widgets are notorious for reprinting on every single page.

5. Expanding links so URLs are visible

On paper a link is just underlined text, so the destination is lost. The :after pseudo-element with attr(href) writes the URL next to the anchor text. Filter out internal anchors, mailto:, tel: and JavaScript links, otherwise you get noise like (#section-2).

If a page has hundreds of links, printing them all becomes unreadable. In that case limit the rule to the article body only:

.entry-content a[href^="http"]:after {
  content: " (" attr(href) ")";
}

6. Page breaks: the modern properties

The old page-break-* properties are still supported, but the current standard is the break-* family. Keep both for maximum compatibility.

Goal Modern property Legacy equivalent
Start a new page before an element break-before: page page-break-before: always
Never split an element across pages break-inside: avoid page-break-inside: avoid
Keep a heading with the text below it break-after: avoid page-break-after: avoid
Avoid a single line stranded at the bottom orphans: 3 Same property
Avoid a single line stranded at the top widows: 3 Same property

Note: orphans and widows are honored by Chrome and Safari, and support in Firefox is inconsistent. Treat them as a progressive enhancement. There is more on it in How To Set Up A Print Style Sheet.

7. Tables that span several pages

thead { display: table-header-group; } makes the browser repeat the header row at the top of each printed page. Combine it with tr { break-inside: avoid; } so a row is never cut in half. For long code blocks, white-space: pre-wrap prevents horizontal overflow, which on paper simply means missing text.

web design

How to test your print stylesheet in Chrome DevTools

Never test by actually printing. Use the emulation tools instead.

Method 1: emulate the print media type (best for live editing)

  1. Open DevTools with F12 or Cmd + Option + I.
  2. Press Ctrl + Shift + P (or Cmd + Shift + P) to open the command menu.
  3. Type Rendering and choose Show Rendering.
  4. Scroll to Emulate CSS media type and select print.
  5. The page now renders with your print styles, and you can inspect and edit elements live in the Elements panel.

This is the fastest loop: change a rule, see the paper version instantly, no reload needed.

Method 2: the real print preview (best for page breaks)

  1. Press Ctrl + P or Cmd + P.
  2. Set the destination to Save as PDF.
  3. Check the total page count, the margins, and where the breaks land.
  4. Open More settings and toggle Background graphics to see how your page behaves both ways.

Media emulation does not paginate, so it will not reveal a table split across two pages. Always finish your testing in the real preview.

Cross-browser checks worth doing

  • Firefox: also has a media emulation toggle in the Inspector rules panel, and its own print preview.
  • Safari: handles @page margins differently, verify at least one document there.
  • Mobile: printing from a phone usually goes through the same engine, so a good desktop result is normally a good mobile result.
  • Paper sizes: preview in both A4 and Letter if you have an international audience.
web design

WordPress: where to enqueue the print stylesheet

Do not edit the parent theme stylesheet, an update will wipe it. Use a child theme or a small custom plugin.

Option A: enqueue a print.css file (recommended)

Create /wp-content/themes/your-child-theme/css/print.css with the CSS from above, then add this to the child theme functions.php:

<?php
function mysite_print_styles() {
    wp_enqueue_style(
        'mysite-print',
        get_stylesheet_directory_uri() . '/css/print.css',
        array(),
        filemtime( get_stylesheet_directory() . '/css/print.css' ),
        'print'
    );
}
add_action( 'wp_enqueue_scripts', 'mysite_print_styles' );

Key points:

  • The fifth argument, 'print', is the media attribute. WordPress outputs media="print" automatically.
  • Using filemtime() as the version string busts the cache every time you edit the file, which saves a lot of confusion during development.
  • Use get_stylesheet_directory_uri() in a child theme and get_template_directory_uri() in a standalone theme.

Option B: inline print CSS without a file

If you only need a dozen rules, attach them to an existing handle:

<?php
function mysite_inline_print_css() {
    $css = '@media print {
        .site-header, .sidebar, .comments-area, .no-print { display: none !important; }
        body { color: #000 !important; background: #fff !important; font-size: 12pt; }
    }';
    wp_add_inline_style( 'mysite-main', $css );
}
add_action( 'wp_enqueue_scripts', 'mysite_inline_print_css', 20 );

Replace mysite-main with the handle your theme already registers. If the handle is wrong, nothing is output, so check with View Source.

Option C: block themes and the Site Editor

On a block theme, Appearance > Editor > Styles > Additional CSS (or Appearance > Customize > Additional CSS on classic themes) accepts an @media print block directly. It is the quickest path, but the CSS lives in the database instead of your repository, so it is harder to version and easy to forget. Fine for a quick fix, not ideal for a maintained site.

Finding the right selectors in WordPress

Common WordPress and block theme classes worth hiding:

Element Typical selector
Admin bar (logged-in users) #wpadminbar
Site header and menu .site-header, .wp-block-navigation
Sidebar and widgets .widget-area, #secondary, .wp-block-sidebar
Comments .comments-area, .wp-block-comments
Post navigation .post-navigation, .wp-block-post-navigation-link
Footer .site-footer, .wp-block-template-part.site-footer

You can also add a class to any individual block: select the block, open Advanced in the sidebar, and type no-print in the Additional CSS class(es) field.

Common mistakes that ruin a print stylesheet

  • Forgetting fixed and sticky elements. A sticky header repeats on every page. Reset positioning.
  • Relying on background images. Browsers strip backgrounds by default. Anything meaningful must be an <img> in the HTML.
  • Hiding content that is loaded lazily. Images below the fold that only load on scroll may print blank. Set loading="eager" on critical images or trigger a load before printing.
  • Using vh and vw units. There is no viewport on paper, results are unpredictable. Use %, mm or pt.
  • Printing every URL. Great for references, terrible for a page with a hundred inline links. Scope the rule.
  • Testing only in one browser. Pagination differs between engines more than screen rendering does.
  • Leaving display: none on a parent when you only wanted to hide a child. Hidden parents take their content with them.
web design

Quick launch checklist

  1. Create print.css and wrap everything in @media print.
  2. Load it with media="print", or enqueue it in WordPress with the 'print' media argument.
  3. Hide navigation, sidebar, ads, forms, comments and footer.
  4. Force black text on a white background, switch to point units.
  5. Give the article the full page width and kill fixed positioning.
  6. Append URLs to external links only.
  7. Add break-inside: avoid to images, tables, code blocks and headings.
  8. Repeat table headers with display: table-header-group.
  9. Preview in Chrome DevTools media emulation, then in the real print dialog.
  10. Save as PDF and read the whole document once before shipping.

FAQ

Does a print stylesheet also apply when saving a page as PDF?

Yes. Browser “Save as PDF” goes through the exact same print pipeline, so your @media print rules apply. That covers the majority of real usage today, since far more people export to PDF than send pages to a physical printer.

Why are my background colors missing in the printout?

Browsers disable background graphics by default to save ink. The user can enable them in the print dialog, or you can request them per element with print-color-adjust: exact plus the -webkit- prefix. Do not depend on it, because the user setting can still win.

Should I use page-break-after or break-after?

Write break-after as the standard property and keep page-break-after right after it as a fallback. Modern browsers alias the legacy properties, but declaring both costs nothing and protects against older rendering engines still in the wild.

How do I force a page break in the middle of an article?

Add an empty element with a class, for example <div class="page-break"></div>, and target it with break-before: page inside your print media query. In WordPress, a Separator or Spacer block with the page-break custom class does the job.

Can I detect printing with JavaScript?

Yes. Use the beforeprint and afterprint events, or window.matchMedia('print') with a change listener. It is useful for expanding accordions, loading lazy images or rendering charts before the page is captured. Keep the CSS as the primary layer and JavaScript as an enhancement.

Is a print stylesheet still worth it if my traffic is mostly mobile?

Yes, for two reasons. Recipes, invoices, tickets, documentation and legal pages get printed or exported constantly, and a clean PDF export makes your content easier to archive and share. It is a small, one-time investment that keeps paying off on every page of the site.

Where should the print stylesheet live in a headless or framework project?

Same principle: a dedicated print.css imported in your global stylesheet, or a component-level @media print block if you use CSS modules. Just make sure your build tool does not tree-shake the rules away because they never match on screen.

Recent Posts

No Posts Found!

Categories

Tags

    Subscribe

    You have been successfully Subscribed! Ops! Something went wrong, please try again.

    About Us

    Express Jam Studio was founded in 2004 by John Smith. John had previously worked for a courier company, but he saw an opportunity to start his own business in the web design and development industry.

    Contact Info

    Copyright © 2022 Express Jam Studio. All Rights Reserved.