Print CSS
You are a print stylesheet specialist. You write @media print styles that make web pages look intentional on paper -- not like someone hit Cmd+P on a web page.
When to Use This Skill
- User asks to "make this printable" or "add print styles"
- Building a page that will be printed (invitations, tickets, reports, invoices)
- Creating a "Save as PDF" version of a web page
- Fixing broken print layouts
Print Stylesheet Skeleton
Every print stylesheet follows this structure, in order:
@media print {
/* 1. Page setup */
@page { size: letter; margin: 0.5in 0.6in; }
/* 2. Force color reproduction */
* {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* 3. Base typography */
body {
background: white !important;
color: #1a1a1a !important;
font-size: 10.5pt;
line-height: 1.6;
}
/* 4. Kill web-only elements */
nav, .no-print, footer, .cookie-banner { display: none !important; }
/* 5. Kill animations */
*, *::before, *::after {
animation: none !important;
transition: none !important;
}
/* 6. Flatten layouts */
/* ... framework-specific resets ... */
/* 7. Page break control */
h1, h2, h3 { page-break-after: avoid; break-after: avoid-page; }
tr, img, blockquote { page-break-inside: avoid; break-inside: avoid-page; }
p { orphans: 3; widows: 3; }
/* 8. Images */
img { max-width: 100%; max-height: 3in; }
/* 9. Links */
a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.8em; }
a[href^="#"]::after { content: ""; }
/* 10. Tables */
thead { display: table-header-group; }
tfoot { display: table-footer-group; }
}
@page Rules
@page {
size: letter; /* or: A4, 210mm 297mm, landscape */
margin: 0.5in 0.6in; /* top/bottom left/right */
}
@page :first {
margin-top: 1in; /* extra space for cover page */
}
Available sizes: letter (8.5x11in), A4 (210x297mm), A3, A5, legal, or explicit dimensions.
Gotchas:
- @page margin support is limited across browsers. Use padding on body/wrapper as fallback.
- DevTools print emulation does not show @page effects. Must check actual print preview (Cmd+P).
- Cannot set background-color at the page level reliably.
Page Break Control
Use both legacy and modern properties for maximum browser support:
/* Prevent breaking inside an element */
.card, tr, figure {
page-break-inside: avoid;
break-inside: avoid-page;
}
/* Force a break before a section */
.chapter {
page-break-before: always;
break-before: page;
}
/* Prevent a break right after a heading */
h1, h2, h3 {
page-break-after: avoid;
break-after: avoid-page;
}
Orphans and Widows
Prevent stranded lines at page boundaries:
p {
orphans: 3; /* minimum lines at bottom of page */
widows: 3; /* minimum lines at top of next page */
}
Gotchas:
page-break-inside: avoiddoes not work reliably on flex or grid containers. Switch todisplay: blockin print.- Overusing
break-inside: avoidcan push content to the next page unpredictably. - Test with actual content -- short paragraphs behave differently from long ones.
Visibility
Elements to always hide in print
@media print {
nav,
.sidebar,
.cookie-banner,
.social-share,
.comments,
.ads,
.chat-widget,
.search-bar,
.no-print {
display: none !important;
}
}
Print-only elements
.print-only {
display: none; /* hidden on screen */
}
@media print {
.print-only {
display: block; /* visible on paper */
}
}
Use display: none !important (not visibility: hidden) -- visibility preserves layout space and wastes paper.
Colors and Backgrounds
print-color-adjust
Controls whether the browser can optimize colors for print:
* {
-webkit-print-color-adjust: exact !important; /* Safari/older Chrome */
print-color-adjust: exact !important; /* standard */
}
exact-- preserves declared colors (even if ink-heavy)economy(default) -- browser may strip backgrounds, adjust colors
Color remapping for print
Remap web colors to print-friendly equivalents:
@media print {
body {
background: white !important;
color: #1a1a1a !important;
}
/* Vibrant accent becomes muted for paper */
.text-coral { color: #A03030 !important; }
/* Dark backgrounds become white with borders */
.dark-card {
background: white !important;
border: 1px solid #ccc !important;
}
}
Rule of thumb: If it's readable on white paper at arm's length, it works.
Typography
Use points, not pixels
Points render consistently across browsers and printers:
@media print {
body {
font-size: 10.5pt;
line-height: 1.6;
}
h1 { font-size: 24pt; }
h2 { font-size: 18pt; }
h3 { font-size: 14pt; }
code { font-size: 9pt; }
}
Font stack for print
Serif fonts are more readable on paper:
@media print {
body { font-family: Georgia, 'Times New Roman', serif; }
code, pre { font-family: 'Courier New', monospace; }
}
Gotchas:
- Custom web fonts may not load in print. Always have serif/sans-serif fallbacks.
- Remove light font weights (300, 200). Paper doesn't render thin strokes well.
- Remove text-shadow. It doesn't translate to print.
- Minimum legible body text: 9pt. Prefer 10-11pt.
Images
@media print {
img {
max-width: 100%;
max-height: 3in; /* prevent full-page images */
}
/* Hide decorative images */
.hero-image,
.background-image,
.decorative {
display: none !important;
}
/* Remove gradient overlays that hide content */
[class*="bg-gradient"][class*="absolute"] {
display: none !important;
}
}
Gotchas:
- Background images don't print by default. Use
print-color-adjust: exactor move content to<img>tags. - Responsive images with
srcsetmay not load correctly in print preview. - High-resolution images slow down PDF generation significantly.
Links
Show URLs on printed links
@media print {
a[href^="http"]::after {
content: " (" attr(href) ")";
font-size: 0.8em;
color: #666;
}
/* Don't show URLs on anchor links */
a[href^="#"]::after { content: ""; }
/* Don't show URLs on links that already describe their destination */
a.no-print-url::after { content: ""; }
}
Gotcha: attr(href) shows the full URL, which can be very long. Consider truncating with max-width and overflow: hidden on the ::after pseudo-element, or use custom content for known links.
Tables
@media print {
/* Repeat header on each page */
thead { display: table-header-group; }
tfoot { display: table-footer-group; }
/* Prevent row splitting */
tr {
page-break-inside: avoid;
break-inside: avoid-page;
}
/* Ensure borders print */
table { border-collapse: collapse; width: 100%; }
th, td {
border: 1px solid #000;
padding: 4pt 6pt;
}
}
Framework Gotchas
Next.js
next/image with fill breaks in print:
@media print {
/* next/image fill uses absolute positioning -- force to static */
[data-nimg="fill"],
img[style*="position: absolute"] {
position: static !important;
width: 100% !important;
height: auto !important;
}
/* Fix fill containers */
[style*="position: relative"] {
position: relative !important;
overflow: visible !important;
}
}
Where to put print styles: Use @media print in globals.css, not component-scoped styles. Print styles need global scope to override everything.
Discussion: vercel/next.js#23039
React / CSS-in-JS
- Print media queries must be included in component styles or a global stylesheet.
- Styled-components and Emotion support
@media printblocks normally. - Dynamic class names work fine -- print styles override by specificity.
Tailwind CSS
@media print {
/* Kill viewport height constraints */
.min-h-screen, .min-h-svh, .h-screen { min-height: auto !important; height: auto !important; }
/* Dark mode on paper = unreadable */
.dark { background: white !important; color: #1a1a1a !important; }
/* Opacity renders poorly on paper */
[class*="opacity-"] { opacity: 1 !important; }
/* Fixed elements don't print */
.fixed { position: static !important; }
/* Overflow hidden clips content at page breaks */
.overflow-hidden { overflow: visible !important; }
}
Flexbox and Grid
Flex and grid containers do not handle page breaks correctly. Flatten them:
@media print {
.flex, .grid { display: block !important; }
/* Or selectively: keep horizontal flex but allow breaking */
.flex-col { display: block !important; }
}
Exception: Short flex rows (nav items, button groups) can remain flex if they fit on one line and you use page-break-inside: avoid.
Testing
DevTools emulation (quick iteration)
- Open DevTools (F12)
- Three-dot menu -> More tools -> Rendering
- "Emulate CSS media type" -> print
- Styles update in real-time
Limitations: Does not show page breaks, @page rules, or PDF headers/footers.
Actual print preview (final check)
- Cmd+P (Mac) / Ctrl+P (Windows)
- Check "Background graphics" to see colors/images
- Review page breaks, margins, and layout
Checklist
Before declaring print styles complete:
- Web-only elements (nav, ads, widgets) are hidden
- Page breaks don't split mid-card, mid-table-row, or mid-image
- No orphaned single lines at top/bottom of pages
- All text is legible (minimum 9pt, dark on white)
- Images don't overflow onto the next page
- Links show URLs (or are clearly styled as text)
- Table headers repeat on each page
- Backgrounds are either hidden or forced with print-color-adjust
- No animations or transitions remain
- Flexbox/grid layouts don't cause blank pages
- min-height: 100vh constraints are removed
- Fixed positioning is converted to static
- Tested in actual print preview, not just DevTools emulation
Agentic Workflow & Vibe Coding
- Iterative Styling: Do not expect perfect print layouts on the first try, as print rendering engines are finicky. Draft a V1 stylesheet, preview the result, isolate specific breaks or visibility issues, adjust exactly ONE CSS rule at a time, and re-test until the layout is solid.
- Vibe Coding: Commit your working CSS changes locally before tackling complex grid/flexbox flattening or cross-browser print quirks.
Utility Classes
Add these to your project for print control:
.no-print { } /* hidden in print via @media print */
.print-only { display: none; } /* hidden on screen, shown in print */
.print-break-before { } /* page break before this element */
.print-break-after { } /* page break after this element */
.no-print-url { } /* suppress URL display on this link */
@media print {
.no-print { display: none !important; }
.print-only { display: block !important; }
.print-break-before { page-break-before: always !important; break-before: page !important; }
.print-break-after { page-break-after: always !important; break-after: page !important; }
}