
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.
| Property | Common values | Use for |
|---|---|---|
break-before | auto, page, avoid-page | Starting sections on a new page |
break-after | auto, page, avoid-page | Keeping headings with following content |
break-inside | auto, avoid, avoid-page | Keeping figures, cards, and rows intact |
orphans | a number (default 2) | Minimum lines left at the bottom of a page |
widows | a 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 + PorCmd + 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 + PorCmd + Shift + P), type "Rendering", and set Emulate CSS media type toprint. 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
@pagesupport vary more than most CSS.
Common Mistakes
- Forgetting
print-color-adjuston essential colored elements, so a status badge or chart disappears on paper. - Leaving fixed or sticky headers visible. A
position: fixedelement can repeat on every printed page in some browsers. Hide it or setposition: static. - Keeping viewport units.
100vhsections can produce blank or oddly sized pages. Reset heights toautoin 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.


