CSSmediumbest-practices
Include a print stylesheet
rule · css-print
A print stylesheet ensures web content is properly formatted when printed, removing digital-only elements and optimizing typography and layout for physical media.
Code Examples
HTML Link Tag
HTML
<!-- Method 1: Separate print stylesheet -->
<link rel="stylesheet" href="print.css" media="print">
<!-- Method 2: All styles in one file -->
<link rel="stylesheet" href="styles.css">
<!-- Method 3: Combined with screen styles -->
<link rel="stylesheet" href="main.css" media="screen, print">CSS Media Query
CSS
/* Method 1: Separate print styles */
@media print {
/* Print-specific styles here */
}
/* Method 2: Import print styles */
@import url('print.css') print;
/* Method 3: Screen vs Print comparison */
@media screen {
.print-only { display: none; }
}
@media print {
.screen-only { display: none; }
.print-only { display: block; }
}Why It Matters
Print stylesheets ensure content is readable when printed—removing digital clutter and optimizing for paper saves ink and improves accessibility.
Complete Print Stylesheet
Essential Print CSS
CSS
/* print.css */
@media print {
/* Reset and base styles */
* {
-webkit-print-color-adjust: exact !important;
color-adjust: exact !important;
print-color-adjust: exact !important;
}
html {
font-size: 12pt;
line-height: 1.4;
}
body {
margin: 0;
padding: 0;
font-family: "Times New Roman", Times, serif;
font-size: 12pt;
line-height: 1.4;
color: #000;
background: #fff;
}
/* Page setup */
@page {
margin: 2cm;
size: A4;
@top-left {
content: "Company Name";
font-size: 10pt;
color: #666;
}
@top-right {
content: "Page " counter(page) " of " counter(pages);
font-size: 10pt;
color: #666;
}
@bottom-center {
content: attr(title);
font-size: 10pt;
color: #666;
}
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
color: #000;
page-break-after: avoid;
break-after: avoid;
font-weight: bold;
margin-top: 0;
}
h1 { font-size: 18pt; margin-bottom: 12pt; }
h2 { font-size: 16pt; margin-bottom: 10pt; }
h3 { font-size: 14pt; margin-bottom: 8pt; }
h4 { font-size: 12pt; margin-bottom: 6pt; }
p {
margin: 0 0 6pt 0;
orphans: 3;
widows: 3;
}
/* Lists */
ul, ol {
margin: 0 0 6pt 18pt;
padding: 0;
}
li {
margin-bottom: 3pt;
}
/* Tables */
table {
border-collapse: collapse;
width: 100%;
margin-bottom: 12pt;
page-break-inside: avoid;
}
th, td {
border: 1pt solid #000;
padding: 4pt;
text-align: left;
vertical-align: top;
}
th {
background: #f0f0f0;
font-weight: bold;
}
/* Images */
img {
max-width: 100%;
height: auto;
page-break-inside: avoid;
}
/* Links */
a {
color: #000;
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
font-size: 10pt;
color: #666;
}
a[href^="mailto:"]:after {
content: " (" attr(href) ")";
}
a[href^="tel:"]:after {
content: " (" attr(href) ")";
}
/* Hide digital-only elements */
.no-print,
.screen-only,
nav,
.navigation,
.sidebar,
.ads,
.advertisement,
.social-media,
.share-buttons,
.comments,
.footer-links,
.breadcrumbs,
.pagination,
.search-form,
.filters,
.toolbar,
.modal,
.popup,
.tooltip,
.dropdown,
.carousel-controls,
.video-controls,
button[type="submit"],
input[type="button"],
input[type="submit"],
.btn,
.button {
display: none !important;
}
/* Show print-only elements */
.print-only {
display: block !important;
}
/* Page breaks */
.page-break-before {
page-break-before: always;
break-before: page;
}
.page-break-after {
page-break-after: always;
break-after: page;
}
.page-break-inside-avoid {
page-break-inside: avoid;
break-inside: avoid;
}
/* Article/content specific */
article,
.content,
.main-content {
width: 100%;
margin: 0;
padding: 0;
float: none;
}
/* Footer adjustments */
footer {
border-top: 1pt solid #ccc;
padding-top: 6pt;
margin-top: 12pt;
font-size: 10pt;
}
}Framework-Specific Implementation
React Print Component
JSX
// components/PrintStyles.jsx
import { useEffect } from 'react'
function PrintStyles() {
useEffect(() => {
// Inject print styles dynamically
const printStyles = `
@media print {
.no-print { display: none !important; }
.print-only { display: block !important; }
body { font-size: 12pt; }
h1 { font-size: 18pt; }
h2 { font-size: 16pt; }
@page {
margin: 2cm;
size: A4;
}
}
`
const styleSheet = document.createElement('style')
styleSheet.type = 'text/css'
styleSheet.innerText = printStyles
styleSheet.media = 'print'
document.head.appendChild(styleSheet)
return () => {
document.head.removeChild(styleSheet)
}
}, [])
return null
}
// components/PrintWrapper.jsx
function PrintWrapper({ children, title }) {
const handlePrint = () => {
window.print()
}
return (
<div className="print-wrapper">
<div className="no-print print-controls">
<button onClick={handlePrint} className="btn btn-print">
🖨️ Print this page
</button>
</div>
<div className="print-content">
<header className="print-only print-header">
<h1>{title}</h1>
<p>Printed on: {new Date().toLocaleDateString()}</p>
</header>
{children}
<footer className="print-only print-footer">
<p>© 2025 Company Name. All rights reserved.</p>
</footer>
</div>
</div>
)
}
// Usage
function ArticlePage() {
return (
<PrintWrapper title="Article Title">
<PrintStyles />
<article>
<h1>Article Title</h1>
<p>Article content...</p>
<div className="no-print social-sharing">
<button>Share on Twitter</button>
<button>Share on Facebook</button>
</div>
<div className="print-only">
<p>Visit our website at: https://example.com</p>
</div>
</article>
</PrintWrapper>
)
}Next.js Print Implementation
JSX
// pages/_document.js
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head>
<link rel="stylesheet" href="/css/print.css" media="print" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
// components/PrintLayout.jsx
import Head from 'next/head'
function PrintLayout({ children, pageTitle }) {
const printDate = new Date().toLocaleDateString()
return (
<>
<Head>
<style jsx>{`
@media print {
.print-header {
display: block;
border-bottom: 1pt solid #000;
margin-bottom: 12pt;
padding-bottom: 6pt;
}
.print-date {
font-size: 10pt;
color: #666;
}
@page {
@top-left {
content: "${pageTitle}";
}
@top-right {
content: "Page " counter(page);
}
}
}
@media screen {
.print-header {
display: none;
}
}
`}</style>
</Head>
<div className="print-header">
<h1>{pageTitle}</h1>
<p className="print-date">Printed: {printDate}</p>
</div>
{children}
</>
)
}
// Usage in pages
export default function BlogPost({ post }) {
return (
<PrintLayout pageTitle={post.title}>
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
<div className="no-print">
<ShareButtons post={post} />
<Comments postId={post.id} />
</div>
</article>
</PrintLayout>
)
}Vue.js Print Directive
VUE
<!-- PrintableContent.vue -->
<template>
<div class="printable-content">
<div class="no-print print-controls">
<button @click="printPage" class="btn-print">
Print Page
</button>
</div>
<div class="content" v-print-styles>
<slot />
</div>
<div class="print-only print-footer">
<p>Printed from: {{ currentUrl }}</p>
<p>Date: {{ printDate }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'PrintableContent',
computed: {
currentUrl() {
return window.location.href
},
printDate() {
return new Date().toLocaleDateString()
}
},
methods: {
printPage() {
window.print()
}
},
directives: {
printStyles: {
inserted(el) {
// Add print-specific classes
el.classList.add('print-optimized')
}
}
}
}
</script>
<style>
@media print {
.print-optimized {
font-family: 'Times New Roman', serif;
font-size: 12pt;
line-height: 1.4;
}
.print-optimized h1 {
font-size: 18pt;
margin-bottom: 12pt;
}
.print-optimized p {
margin-bottom: 6pt;
}
}
</style>Advanced Print Features
CSS Page Rules
CSS
@media print {
/* Different page layouts */
@page :first {
margin-top: 3cm;
@top-left {
content: "";
}
@top-center {
content: "Document Title";
font-size: 14pt;
font-weight: bold;
}
}
@page :left {
margin-left: 3cm;
margin-right: 2cm;
@bottom-left {
content: counter(page);
}
}
@page :right {
margin-left: 2cm;
margin-right: 3cm;
@bottom-right {
content: counter(page);
}
}
/* Named page layouts */
@page chapter {
margin: 3cm;
@top-center {
content: "Chapter " counter(chapter);
}
}
@page appendix {
margin: 2cm;
@top-center {
content: "Appendix";
}
}
/* Apply named pages */
.chapter {
page: chapter;
page-break-before: always;
}
.appendix {
page: appendix;
page-break-before: always;
}
}Print-Specific JavaScript
JavaScript
// print-handler.js
class PrintHandler {
constructor() {
this.beforePrintCallbacks = []
this.afterPrintCallbacks = []
this.setupEventListeners()
}
setupEventListeners() {
window.addEventListener('beforeprint', () => {
this.handleBeforePrint()
})
window.addEventListener('afterprint', () => {
this.handleAfterPrint()
})
// For browsers that don't support beforeprint/afterprint
if (window.matchMedia) {
const mediaQueryList = window.matchMedia('print')
mediaQueryList.addEventListener('change', (mql) => {
if (mql.matches) {
this.handleBeforePrint()
} else {
this.handleAfterPrint()
}
})
}
}
handleBeforePrint() {
// Execute before print callbacks
this.beforePrintCallbacks.forEach(callback => callback())
// Common before-print tasks
this.expandCollapsedSections()
this.showFullUrls()
this.addPrintTimestamp()
}
handleAfterPrint() {
// Execute after print callbacks
this.afterPrintCallbacks.forEach(callback => callback())
// Restore original state
this.collapseExpandedSections()
this.hideFullUrls()
this.removePrintTimestamp()
}
expandCollapsedSections() {
const collapsible = document.querySelectorAll('.collapsible[data-collapsed="true"]')
collapsible.forEach(element => {
element.setAttribute('data-print-expanded', 'true')
element.setAttribute('data-collapsed', 'false')
})
}
collapseExpandedSections() {
const printExpanded = document.querySelectorAll('[data-print-expanded="true"]')
printExpanded.forEach(element => {
element.setAttribute('data-collapsed', 'true')
element.removeAttribute('data-print-expanded')
})
}
showFullUrls() {
const links = document.querySelectorAll('a[href]')
links.forEach(link => {
if (!link.textContent.includes(link.href)) {
const url = document.createElement('span')
url.className = 'print-url'
url.textContent = ` (${link.href})`
link.appendChild(url)
}
})
}
hideFullUrls() {
const printUrls = document.querySelectorAll('.print-url')
printUrls.forEach(url => url.remove())
}
addPrintTimestamp() {
const timestamp = document.createElement('div')
timestamp.className = 'print-timestamp'
timestamp.textContent = `Printed: ${new Date().toLocaleString()}`
document.body.insertBefore(timestamp, document.body.firstChild)
}
removePrintTimestamp() {
const timestamp = document.querySelector('.print-timestamp')
if (timestamp) {
timestamp.remove()
}
}
onBeforePrint(callback) {
this.beforePrintCallbacks.push(callback)
}
onAfterPrint(callback) {
this.afterPrintCallbacks.push(callback)
}
print() {
window.print()
}
}
// Usage
const printHandler = new PrintHandler()
printHandler.onBeforePrint(() => {
console.log('Preparing for print...')
// Custom logic before printing
})
printHandler.onAfterPrint(() => {
console.log('Print dialog closed')
// Custom logic after printing
})Testing Print Styles
Browser Testing
JavaScript
// print-style-tester.js
class PrintStyleTester {
async testPrintStyles(url) {
// Create a hidden iframe for testing
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = url
document.body.appendChild(iframe)
return new Promise((resolve) => {
iframe.onload = () => {
const iframeDoc = iframe.contentDocument
const printStyles = this.extractPrintStyles(iframeDoc)
const results = {
hasPrintStyles: printStyles.length > 0,
printStylesheets: printStyles,
hiddenElements: this.findHiddenElements(iframeDoc),
pageBreaks: this.findPageBreaks(iframeDoc),
typography: this.analyzeTypography(iframeDoc)
}
document.body.removeChild(iframe)
resolve(results)
}
})
}
extractPrintStyles(doc) {
const printStyles = []
// Check for print media stylesheets
const links = doc.querySelectorAll('link[rel="stylesheet"][media*="print"]')
links.forEach(link => {
printStyles.push({
type: 'external',
href: link.href,
media: link.media
})
})
// Check for @media print in style tags
const styles = doc.querySelectorAll('style')
styles.forEach(style => {
if (style.textContent.includes('@media print')) {
printStyles.push({
type: 'inline',
content: style.textContent
})
}
})
return printStyles
}
findHiddenElements(doc) {
const hiddenSelectors = [
'.no-print',
'.screen-only',
'nav',
'.navigation',
'.sidebar'
]
const hiddenElements = []
hiddenSelectors.forEach(selector => {
const elements = doc.querySelectorAll(selector)
if (elements.length > 0) {
hiddenElements.push({
selector,
count: elements.length
})
}
})
return hiddenElements
}
findPageBreaks(doc) {
const pageBreakSelectors = [
'.page-break-before',
'.page-break-after',
'.page-break-inside-avoid'
]
const pageBreaks = []
pageBreakSelectors.forEach(selector => {
const elements = doc.querySelectorAll(selector)
if (elements.length > 0) {
pageBreaks.push({
selector,
count: elements.length
})
}
})
return pageBreaks
}
analyzeTypography(doc) {
const headings = doc.querySelectorAll('h1, h2, h3, h4, h5, h6')
const paragraphs = doc.querySelectorAll('p')
return {
headings: headings.length,
paragraphs: paragraphs.length,
hasSerif: this.checkFontFamily(doc, 'serif'),
fontSize: this.checkFontSizes(doc)
}
}
checkFontFamily(doc, family) {
const body = doc.body
const computedStyle = window.getComputedStyle(body)
return computedStyle.fontFamily.includes(family)
}
checkFontSizes(doc) {
const body = doc.body
const computedStyle = window.getComputedStyle(body)
return {
body: computedStyle.fontSize,
root: computedStyle.fontSize
}
}
}
// Usage
const tester = new PrintStyleTester()
tester.testPrintStyles('https://example.com').then(results => {
console.log('Print Style Analysis:', results)
})Automated Testing with Puppeteer
JavaScript
// print-test.js
const puppeteer = require('puppeteer')
async function testPrintStyles(url) {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto(url)
// Generate PDF to test print styles
const pdf = await page.pdf({
format: 'A4',
margin: {
top: '2cm',
bottom: '2cm',
left: '2cm',
right: '2cm'
},
printBackground: true
})
// Analyze print styles
const printAnalysis = await page.evaluate(() => {
const printStylesheets = Array.from(
document.querySelectorAll('link[rel="stylesheet"][media*="print"]')
).map(link => ({
href: link.href,
media: link.media
}))
const printMediaQueries = Array.from(document.querySelectorAll('style'))
.filter(style => style.textContent.includes('@media print'))
.length
const hiddenElements = document.querySelectorAll('.no-print, .screen-only').length
const printOnlyElements = document.querySelectorAll('.print-only').length
return {
hasPrintStyles: printStylesheets.length > 0 || printMediaQueries > 0,
printStylesheets,
printMediaQueries,
hiddenElements,
printOnlyElements
}
})
await browser.close()
return {
analysis: printAnalysis,
pdfGenerated: pdf.length > 0
}
}
// Usage
testPrintStyles('https://example.com').then(results => {
console.log('Print Test Results:', results)
})Best Practices
- Always Provide: Include print styles for all public-facing content
- Hide Unnecessary: Remove navigation, ads, and interactive elements
- Optimize Typography: Use serif fonts and appropriate sizes (12pt)
- Page Breaks: Control where content breaks across pages
- URLs in Links: Show full URLs for external links
- Test Regularly: Verify print styles work across browsers
- Performance: Keep print CSS lightweight and efficient
- Accessibility: Ensure printed content remains accessible
Common Print CSS Patterns
Invoice/Receipt Layout
CSS
@media print {
.invoice {
width: 100%;
margin: 0;
padding: 0;
}
.invoice-header {
border-bottom: 2pt solid #000;
margin-bottom: 12pt;
padding-bottom: 6pt;
}
.invoice-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 12pt;
}
.invoice-table th,
.invoice-table td {
border: 1pt solid #000;
padding: 6pt;
text-align: left;
}
.invoice-total {
font-weight: bold;
font-size: 14pt;
text-align: right;
border-top: 2pt solid #000;
padding-top: 6pt;
}
}Article/Blog Post Layout
CSS
@media print {
.article {
max-width: none;
margin: 0;
}
.article-header {
margin-bottom: 18pt;
page-break-after: avoid;
}
.article-title {
font-size: 20pt;
font-weight: bold;
margin-bottom: 6pt;
}
.article-meta {
font-size: 10pt;
color: #666;
border-bottom: 1pt solid #ccc;
padding-bottom: 6pt;
}
.article-content p {
text-align: justify;
margin-bottom: 8pt;
}
.article-content blockquote {
border-left: 2pt solid #000;
padding-left: 12pt;
margin: 12pt 0;
font-style: italic;
}
}Verification
Automated Checks
- Use browser or CI tooling where applicable to verify the fix.
Manual Checks
- Confirm the rule in the final rendered output or runtime behavior.
- Test one primary path and one edge case affected by the change.
- Re-check shared abstractions so the fix is applied consistently.