Building CSS for print cleanly and maintainably
A well built print stylesheet decides whether a printout is readable or whether navigation, banner ads and cut off tables make the content unusable. With media print, targeted page breaks and visible link targets, a screen view becomes a document that also works on paper.
Table of Contents
- 1. Why print stylesheets still matter in 2026
- 2. Basics: media print and print-color-adjust
- 3. Structuring and including print stylesheets
- 4. Hiding elements and revealing link targets
- 5. Controlling page breaks with break-inside and break-before
- 6. Print typography: sizes, colors, contrast
- 7. Print friendly tables, images and forms
- 8. Testing and debugging print stylesheets
- 9. Print stylesheets compared: approaches and tools
- 10. Summary
- 11. FAQ
1. Why print stylesheets still matter in 2026
Anyone who believes printouts are a relic from before the cloud and PDFs underestimates how often users still put invoices, recipes, travel confirmations or legal documents on paper. A print stylesheet is the only place in the frontend that ensures such a printout is not made up of a cut off navigation bar, a gray cookie banner and a half visible table. Without dedicated print rules, the browser simply takes over the screen layout and squeezes it onto the paper format, often with disastrous results.
Especially in e-commerce and government portals, a clean print stylesheet is not a nice to have but a functional requirement: order confirmations, invoices and shipping labels must print without navigation, without ad space and with correctly visible prices. This article covers the complete construction of a production ready print stylesheet, from the base structure through page breaks to the testing workflow, with a focus on maintainability instead of one off hacks.
2. Basics: media print and print-color-adjust
The entry point of every print stylesheet is the media query @media print. All rules inside this block apply exclusively when printing or when generating a print PDF through the browser, never on screen. This allows a single CSS bundle to be maintained for both screen and print, without loading two separate stylesheet files. Important detail: @media print rules are evaluated by most browsers only when the print dialog is opened, so a live preview is only possible through the print preview itself.
A second, often overlooked tool is the print-color-adjust property (formerly -webkit-print-color-adjust). Browsers save ink by default by removing background colors and images when printing, unless the user manually enables this option. For elements where the background color carries semantic meaning, such as colored status badges or warning notices, print-color-adjust: exact forces the color to be preserved. A good print stylesheet sets this property selectively instead of forcing it globally, because global forcing increases ink consumption unnecessarily.
/* Base print stylesheet entry point */
@media print {
/* Force exact color reproduction only where color carries meaning */
.status-badge,
.price-highlight {
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
/* Everything else falls back to ink-saving default rendering */
body {
background: none;
color: #000;
font-size: 12pt;
line-height: 1.4;
}
/* Remove decorative shadows and gradients that waste toner */
* {
box-shadow: none !important;
text-shadow: none !important;
}
}
3. Structuring and including print stylesheets
There are two common ways to include a print stylesheet: as a separate file with <link rel="stylesheet" media="print" href="print.css"> or as an @media print block inside the existing main stylesheet. The separate file has a performance advantage, because browsers download it but with lower priority, since it is not relevant for the initial rendering. The block inside the main stylesheet is easier to maintain, because print rules sit right next to the corresponding screen rules and are less likely to be forgotten during refactoring.
In larger projects, a cascade layer structure is worth considering: a dedicated @layer print that only activates inside @media print keeps print rules separate from the general specificity cascade and prevents an accidentally more specific screen selector from overriding a print rule. This separation pays off especially in design systems where components are maintained by multiple teams, and nobody should accidentally break a print stylesheet owned by another team.
4. Hiding elements and revealing link targets
The most obvious step in any print stylesheet is hiding elements that make no sense on paper: navigation, search fields, video players, cookie banners and call to action buttons. The rule display: none inside @media print removes these elements entirely from the print flow without reserving layout space for them. More important than the hiding itself is what becomes visible instead: link targets that are blue and underlined on screen effectively disappear on paper, because nobody can click a printout.
The solution is generated content, content: " (" attr(href) ")", combined with the selector a[href^="http"]::after. This print stylesheet pattern writes the actual URL right behind the link text, so a reader of the printout can manually type in the address. Internal anchor links and navigation links should be excluded from this, otherwise the printout gets cluttered with relative paths that would not work anyway.
@media print {
/* Hide screen-only chrome entirely */
nav, .site-header__actions, .cookie-banner,
.video-player, .cta-button, .search-form {
display: none !important;
}
/* Reveal absolute link targets since printed links are not clickable */
a[href^="http"]::after {
content: " (" attr(href) ")";
font-size: 0.85em;
color: #444;
word-break: break-all;
}
/* Skip internal anchors and mailto links, they add no printed value */
a[href^="#"]::after,
a[href^="mailto:"]::after,
nav a::after {
content: "";
}
}
5. Controlling page breaks with break-inside and break-before
Without explicit control, the print driver decides arbitrarily where a page ends, with the result that table rows get cut in half or headings get placed on the last line of a page without the paragraph that follows. The modern property break-inside: avoid instructs the browser not to split an element across a page boundary. Applied to cards, table rows or invoice line items, this ensures every cohesive unit stays fully on one page.
In addition, break-before: page forces an element to always start on a new page, which is useful for chapter headings or separate invoice attachments. The older notation page-break-inside and page-break-before still works in all current browsers, but is considered a legacy alias of the newer break-* properties from the CSS Fragmentation module. A robust print stylesheet sets both notations side by side as long as older rendering engines are still in use.
@media print {
/* Keep invoice line items intact across page boundaries */
.invoice-row,
table tr,
.card {
break-inside: avoid;
page-break-inside: avoid; /* legacy alias for older engines */
}
/* Force a fresh page for each new chapter */
.chapter-start,
.invoice-attachment {
break-before: page;
page-break-before: always;
}
/* Avoid orphaned headings directly above a page break */
h1, h2, h3 {
break-after: avoid;
page-break-after: avoid;
}
}
6. Print typography: sizes, colors, contrast
Screen typography and print typography follow different rules. On screen, relative units such as rem and viewport units dominate, but for a print stylesheet the absolute unit pt (point) is the better choice, because it refers directly to physical paper size and is independent of screen resolution or zoom level. A body text size of 11 to 12pt and a line height of 1.4 to 1.5 are considered proven values for readable printouts.
Color is the second critical point: light text colors on a light background that are still readable on a backlit screen almost disappear on white paper once an inkjet printer reproduces them with reduced saturation. A print stylesheet generally sets body text to color: #000 or a dark gray, independent of the screen color scheme. The CSS properties widows and orphans additionally prevent a single line of text from being stranded at the top or bottom of a page, which happens especially often with multi column body text.
@media print {
body {
font-family: Georgia, "Times New Roman", serif;
font-size: 11pt;
line-height: 1.45;
color: #111;
}
/* Prevent isolated single lines at page top or bottom */
p, li {
orphans: 3;
widows: 3;
}
h1 { font-size: 18pt; }
h2 { font-size: 15pt; }
h3 { font-size: 13pt; }
/* Underline instead of color to preserve emphasis in grayscale print */
strong, .highlight {
text-decoration: underline;
font-weight: 700;
}
}
7. Print friendly tables, images and forms
Tables are the most common source of errors in a print stylesheet, because on screen they are often horizontally scrollable, a feature that simply does not exist on paper. For print, a wide table must either shrink to fit the available page width, with reduced font size and smaller padding, or be converted into a different format such as a definition list. Images should be constrained with max-width: 100% and height: auto, so a high resolution product photo does not extend beyond the page margin and get clipped.
Form elements such as input fields, checkboxes and dropdowns are fundamentally useless on paper, because interactivity is missing. A well thought out print stylesheet either replaces them with their current value as plain text, for example via input::after { content: attr(value); }, or hides them together with their labels entirely when the value is irrelevant for the printout. For filled out forms, such as an order overview with quantity fields, converting to text is the better choice, because it documents the value actually entered.
@media print {
table {
width: 100%;
font-size: 10pt;
border-collapse: collapse;
}
table th, table td {
padding: 4pt 6pt;
border: 0.5pt solid #999;
}
img {
max-width: 100% !important;
height: auto !important;
break-inside: avoid;
}
/* Replace interactive form fields with their plain text value */
input[type="text"], input[type="number"], select {
border: none;
background: none;
}
input[type="text"]::after,
input[type="number"]::after {
content: attr(value);
font-weight: 600;
}
}
8. Testing and debugging print stylesheets
The fastest way to test a print stylesheet is the browser's print preview, usually via Ctrl+P or Cmd+P, combined with "Save as PDF" instead of actual paper printing. Chrome DevTools additionally offer a rendering emulation: the command palette entry "Emulate CSS media type" lets you simulate print as the active medium, while the remaining DevTools features such as the element inspector and computed styles keep working normally. This saves constantly switching to the real print dialog for every small CSS change.
A common debugging mistake: developers test exclusively in Chrome, even though Firefox and Safari differ noticeably in page break handling and in the default header with URL and date. A complete test of a print stylesheet covers at least these three engines, including the browser specific headers and footers, which can be suppressed via @page { margin: 0; } but do not behave identically in every browser.
9. Print stylesheets compared: approaches and tools
Depending on project size and requirements, different strategies suit a print stylesheet, ranging from a lean media query solution to a dedicated rendering service for complex PDF documents.
| Approach | Effort | Suitable for | Limitation |
|---|---|---|---|
| @media print block | Low | Blog articles, landing pages | No access to page numbering |
| Separate print.css | Medium | Larger sites with clear separation | Additional HTTP request |
| @page rules (covered in the next article) | Medium | Invoices, formal documents | Inconsistent browser support for margin boxes |
| Headless PDF renderer | High | Legally compliant PDF exports, bulk printing | Requires server side infrastructure |
| Paged.js / Vivliostyle | High | Books, multi column documents | Additional JS dependency in the build |
For most marketing and content pages, a simple @media print block inside the existing stylesheet is entirely sufficient. However, as soon as fixed page sizes, recurring headers and footers or exact page numbering are required, for example for invoices or contracts, there is no way around @page rules or a dedicated rendering tool. The print stylesheet is in that case only half the solution, the pagination logic handles the other half.
Mironsoft
Frontend development, modern CSS architecture and print optimization
Print stylesheets that actually work?
We build maintainable print stylesheets for invoices, order confirmations and content pages, including a testing workflow across all relevant browsers and a clean separation of screen and print rules.
CSS Audit
Reviewing existing print rules and identifying typical sources of error
Implementation
Implementing page breaks, typography and link visibility production ready
Testing Workflow
Cross browser verification including PDF export and regression tests
10. Summary
A production ready print stylesheet starts with @media print as the base, consistently hides all screen only elements, and makes link targets readable again through generated content. break-inside: avoid keeps related elements together across page boundaries, while typography in pt units and dark text color ensures readability on paper. Tables, images and form fields each need their own adjustments so they are not cut off on a fixed paper width.
The testing workflow determines the actual quality: anyone who checks a print stylesheet in only one browser will miss discrepancies in page breaks and headers. For simple content pages, the media query solution described here is entirely sufficient, while invoices and formal documents are worth examining through @page rules and dedicated pagination tools.
Modern Print Stylesheets — The Essentials at a Glance
Base Structure
@media print as the central entry point, complemented with print-color-adjust: exact for semantically important colors.
Visibility
Hide navigation and interactivity, reveal link targets as text via content: attr(href).
Page Breaks
break-inside: avoid for table rows and cards, break-before: page for new chapters.
Testing
Chrome DevTools media emulation for fast iteration, real testing across at least three browser engines before going live.