Type something to search...
Print Stylesheets: How to Make Web Pages Print-Friendly

Print Stylesheets: How to Make Web Pages Print-Friendly

It's easy to forget that people still print web pages. But they do: recipes, boarding passes, invoices, receipts, event tickets, documentation, legal terms, and long articles they want to read with a pen in hand. Many more "print" to PDF to save or share a page. And when they do, most sites look terrible on paper: navigation menus eating half the first page, cookie banners stamped over the content, dark backgrounds draining ink, and links reduced to meaningless blue underlines.

Fixing this takes surprisingly little work. With a small print stylesheet, you can strip out the clutter, set sensible typography, control page breaks, and even show link URLs in the printed output. In this guide, I'll walk through everything you need to build a print-friendly page with modern CSS.

Why Print Styles Matter

A thoughtful print stylesheet gives you:

  • Better usability for anyone who prints or saves a PDF, which is common for invoices, tickets, and reference material.
  • Lower ink and paper waste, by hiding decorative elements and dropping heavy backgrounds.
  • A more professional feel. A clean printed invoice or report reflects well on your product.
  • Accessibility benefits. Some people find reading long content on paper far easier than on a screen.

It's also low risk. Print styles only apply when printing, so they can't break your screen layout.

How to Target Print

There are two main ways to apply print-only CSS.

Option 1: @media print in Your Main Stylesheet

This is the most common approach. Put print rules at the end of your CSS, or next to the components they affect:

@media print {
  .site-header,
  .site-footer,
  .cookie-banner {
    display: none;
  }
}

Option 2: A Separate Print Stylesheet

You can also load a dedicated file with the media attribute:

<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/print.css" media="print" />

The browser still downloads print.css, but at a low priority, and it doesn't block rendering on screen. This is handy if your print styles are large or maintained separately.

The Reverse: Screen-Only Styles

Sometimes it's easier to scope styles to the screen so they never reach paper at all:

@media screen {
  .hero {
    background: linear-gradient(135deg, #1e1b4b, #312e81);
    color: #f8fafc;
  }
}

Mixing both approaches is fine. Use whichever results in less code for a given component.

Step 1: Hide What Doesn't Belong on Paper

Start by removing everything that only makes sense on screen: navigation, sidebars, ads, share buttons, video embeds, chat widgets, cookie notices, and interactive controls.

@media print {
  .site-header,
  .site-nav,
  .sidebar,
  .site-footer,
  .share-buttons,
  .newsletter-signup,
  .cookie-banner,
  .chat-widget,
  video,
  iframe,
  button {
    display: none !important;
  }
}

This is one of the few places where !important is reasonable. Print rules need to beat whatever display value a component uses on screen, and there's no cascade conflict to worry about, since this block only exists for print.

An even cleaner approach is a utility class you can drop on anything:

@media print {
  .no-print {
    display: none !important;
  }
}

.print-only {
  display: none;
}

@media print {
  .print-only {
    display: block;
  }
}
<nav class="site-nav no-print">...</nav>
<p class="print-only">Printed from example.com/orders/1042 on your request.</p>

Step 2: Reset Colors and Backgrounds

Browsers usually don't print background colors or images by default. They're turned off in the print dialog to save ink. But text colors do print, and light text designed for a dark background can become unreadable. Reset the essentials:

@media print {
  *,
  *::before,
  *::after {
    background: transparent !important;
    color: #000 !important;
    box-shadow: none !important;
    text-shadow: none !important;
  }
}

That's a blunt instrument, which is often exactly what you want for articles and docs. For designed documents such as invoices or tickets, you might need certain colors to survive. Use print-color-adjust on those specific elements:

@media print {
  .invoice-total,
  .status-badge {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

print-color-adjust: exact tells the browser to keep backgrounds and colors on that element even if the user hasn't enabled background graphics. Use it sparingly: it overrides the user's ink-saving preference. Also note that it can't bring back a background you've already stripped with the blunt !important reset above. If you use both, give those elements their print colors in a later rule that also uses !important, or exclude them from the reset with :not(.invoice-total, .status-badge). Chromium-based browsers and Safari have long needed the -webkit- prefix, so include both.

Step 3: Set Print Typography

Screens and paper have different needs. Pixel sizes don't map neatly to paper, while points (pt) are the traditional print unit.

@media print {
  body {
    font-family: Georgia, "Times New Roman", serif;
    font-size: 12pt;
    line-height: 1.5;
  }

  h1 {
    font-size: 24pt;
  }

  h2 {
    font-size: 18pt;
  }

  h3 {
    font-size: 14pt;
  }

  .article,
  main {
    width: 100%;
    max-width: none;
    margin: 0;
    padding: 0;
  }
}

Serif fonts often read well on paper, but it's a style choice. The more important parts are a comfortable size (11 to 12pt is typical) and removing any fixed widths or large side margins, so the content uses the full printable area.

Step 4: Show Link URLs

On screen, a link is clickable. On paper, "click here" is useless. Print the URL after each link using generated content:

@media print {
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    font-weight: normal;
    word-break: break-all;
  }

  /* Don't print URLs for internal jumps or scripts */
  a[href^="#"]::after,
  a[href^="javascript:"]::after {
    content: "";
  }

  /* Skip URLs for links that are just images or buttons */
  .logo a::after,
  a.button::after {
    content: "";
  }
}

The attr(href) function inserts the attribute's value as text. Here I limited it to absolute http links, which covers most external references. If your internal links are relative (for example /pricing), you can add the domain yourself:

@media print {
  a[href^="/"]::after {
    content: " (https://example.com" attr(href) ")";
  }
}

The word-break: break-all is important. Long URLs otherwise overflow the page edge.

Step 5: Control Page Breaks

Nothing looks sloppier than a heading stranded at the bottom of one page with its content starting on the next, or a table split awkwardly down the middle. The fragmentation properties let you control where pages break.

PropertyCommon valuesUse for
break-beforeauto, page, avoid-pageStarting sections on a new page
break-afterauto, page, avoid-pageKeeping headings with following content
break-insideauto, avoid, avoid-pageKeeping figures, cards, and rows intact
orphansa number (default 2)Minimum lines left at the bottom of a page
widowsa number (default 2)Minimum lines carried to the top of a page
@media print {
  /* Keep headings with the content that follows */
  h1,
  h2,
  h3 {
    break-after: avoid-page;
  }

  /* Don't split these across pages */
  figure,
  img,
  pre,
  blockquote,
  table,
  .card {
    break-inside: avoid;
  }

  /* Start each chapter on a new page */
  .chapter {
    break-before: page;
  }

  /* Avoid lonely lines */
  p {
    orphans: 3;
    widows: 3;
  }
}

You may see older properties like page-break-before and page-break-inside in tutorials. They still work as aliases, but the unprefixed break-* properties are the modern standard.

Keep in mind that avoid is a request, not a guarantee. If a table is taller than a page, the browser has to break it somewhere. For long tables, repeat the header row on each page:

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

  tr {
    break-inside: avoid;
  }
}

Most browsers repeat thead rows on each printed page automatically, but declaring it explicitly doesn't hurt.

Step 6: Set Page Size and Margins with @page

The @page rule controls the printed page itself: its size, orientation, and margins.

@page {
  size: A4;
  margin: 18mm 16mm;
}

/* Landscape for wide reports */
@page wide {
  size: A4 landscape;
}

.report-wide {
  page: wide;
}

You can also target the first page, or left and right pages for double-sided printing:

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

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

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

Basic @page with size and margin works in current major browsers. Named pages (the page property) are supported in Chromium-based browsers and Firefox, and support elsewhere has been catching up. Page-margin boxes like @top-center for custom headers and footers are specified and have started landing in Chromium, but they're not yet universal. If you need running headers and page numbers today, check your target browsers first, or reach for a dedicated PDF engine such as Paged.js or a server-side renderer.

Step 7: Handle Images and Layout

Flexbox and grid layouts usually print fine, but multi-column layouts designed for wide screens can get cramped on paper. It's often simpler to stack everything:

@media print {
  .layout,
  .product-grid {
    display: block;
  }

  img {
    max-width: 100% !important;
    height: auto;
  }

  .hero-image {
    max-height: 8cm;
    object-fit: cover;
  }

  /* Expand collapsed content so it prints */
  details {
    display: block;
  }

  details > summary {
    list-style: none;
    font-weight: 700;
  }
}

One catch with details: its content stays hidden when the element is closed, even in print, and CSS alone can't reliably force it open across browsers. If collapsed content matters on paper, open all the details elements with a little JavaScript before printing:

window.addEventListener("beforeprint", () => {
  document.querySelectorAll("details:not([open])").forEach((el) => {
    el.setAttribute("open", "");
    el.dataset.printOpened = "true";
  });
});

window.addEventListener("afterprint", () => {
  document.querySelectorAll("details[data-print-opened]").forEach((el) => {
    el.removeAttribute("open");
    delete el.dataset.printOpened;
  });
});

The beforeprint and afterprint events are also useful for swapping a chart for a static image or expanding a lazy-loaded section.

A Complete Starter Print Stylesheet

Here's a compact, reusable starting point that combines the pieces above:

@page {
  margin: 16mm;
}

@media print {
  .no-print,
  .site-header,
  .site-nav,
  .site-footer,
  .sidebar,
  .cookie-banner,
  video,
  iframe {
    display: none !important;
  }

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

  body {
    font:
      12pt/1.5 Georgia,
      "Times New Roman",
      serif;
  }

  main,
  .container {
    width: auto;
    max-width: none;
    margin: 0;
    padding: 0;
  }

  a {
    text-decoration: underline;
  }

  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    word-break: break-all;
  }

  h1,
  h2,
  h3 {
    break-after: avoid-page;
  }

  figure,
  img,
  pre,
  table,
  blockquote {
    break-inside: avoid;
  }

  img {
    max-width: 100% !important;
  }

  p {
    orphans: 3;
    widows: 3;
  }
}

Testing Print Styles

You don't need to waste paper while you work.

  • Print preview (Ctrl + P or Cmd + P) is the most accurate test. Try "Save as PDF" to inspect the output closely.
  • Chrome DevTools can emulate print media: open the Command Menu (Ctrl + Shift + P or Cmd + Shift + P), type "Rendering", and set Emulate CSS media type to print. You can then inspect print styles like any other CSS.
  • Firefox has a print simulation toggle in the Inspector's rules panel.
  • Test in more than one browser. Page breaking and @page support vary more than most CSS.

Common Mistakes

  • Forgetting print-color-adjust on essential colored elements, so a status badge or chart disappears on paper.
  • Leaving fixed or sticky headers visible. A position: fixed element can repeat on every printed page in some browsers. Hide it or set position: static.
  • Keeping viewport units. 100vh sections can produce blank or oddly sized pages. Reset heights to auto in print.
  • Printing every URL. Showing URLs for navigation, buttons, and anchors adds noise. Limit it to content links.
  • Never testing. Print styles rot quietly because no one looks at them. Add print preview to your QA checklist for pages people actually print.

Conclusion

A print stylesheet is one of the highest-value, lowest-effort improvements you can make to content-heavy pages. Hide the chrome, reset colors, set readable typography, print link URLs, and tell the browser where pages should and shouldn't break. Add @page for margins and orientation, and use beforeprint when a bit of JavaScript is needed.

The next time someone prints your recipe, invoice, or documentation, they'll get a clean, readable page instead of a pile of navigation menus. That's a small detail, but it's the kind people remember.

Tags :
Share :

Related Posts

A Complete Guide to CSS Container Queries

A Complete Guide to CSS Container Queries

For more than a decade, responsive design meant one thing: media queries. You asked the browser how wide the viewport was and adjusted your layout ac

Continue Reading
A Comprehensive Guide to Installing Next.js

A Comprehensive Guide to Installing Next.js

Next.js has emerged as a powerful framework for building React applications, offering features like server-side rendering, static site generation, an

Continue Reading
Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

CSS has evolved significantly, and modern tools like clamp(), min(), and max() are powerful game-changers in dynamic styling. If you’ve struggl

Continue Reading