Web Page Printing Implementation (Print CSS)
A page looks good on screen and terrible when printed — a typical situation. Navigation, advertising banners, chat widgets, sticky headers and background images turn a printout into garbage. This is solved with a separate set of CSS rules and minimal JavaScript.
Basic Print Media Query Structure
/* Main styles file */
@media print {
/* Hide everything unnecessary */
header,
footer,
nav,
aside,
.sidebar,
.cookie-banner,
.chat-widget,
.share-buttons,
.advertisement,
.back-to-top,
[data-noprint],
.no-print {
display: none !important;
}
/* Content full width */
.container,
.wrapper,
main,
article {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
float: none !important;
}
/* Remove shadows and rounded corners */
* {
box-shadow: none !important;
text-shadow: none !important;
border-radius: 0 !important;
}
}
Typography for Print
Screen fonts and print fonts are different things. For paper, serif fonts are preferable:
@media print {
body {
font-family: Georgia, 'Times New Roman', serif;
font-size: 12pt;
line-height: 1.5;
color: #000;
background: #fff;
}
h1 { font-size: 24pt; margin-bottom: 12pt; }
h2 { font-size: 18pt; margin-bottom: 8pt; }
h3 { font-size: 14pt; margin-bottom: 6pt; }
p, li {
font-size: 12pt;
orphans: 3; /* minimum 3 lines before page break */
widows: 3; /* minimum 3 lines after page break */
}
/* Links: display URL */
a[href]:after {
content: ' (' attr(href) ')';
font-size: 10pt;
color: #555;
}
/* Don't show URL for anchors and javascript: */
a[href^='#']:after,
a[href^='javascript:']:after {
content: '';
}
/* Images */
img {
max-width: 100% !important;
page-break-inside: avoid;
}
}
Managing Page Breaks
@media print {
/* Headings don't separate from the next paragraph */
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
break-after: avoid;
}
/* Elements that should be on one page */
.no-break,
table,
figure,
blockquote {
page-break-inside: avoid;
break-inside: avoid;
}
/* Force break before section */
.page-break-before,
section.chapter {
page-break-before: always;
break-before: page;
}
/* Force break after */
.page-break-after {
page-break-after: always;
break-after: page;
}
}
Page Parameters via @page
@page {
size: A4 portrait;
margin: 2cm 2.5cm 2cm 2.5cm;
}
@page :first {
margin-top: 3cm; /* more space on first page */
}
@page :left {
margin-left: 3cm; /* for double-sided printing: binding on left */
}
@page :right {
margin-right: 3cm;
}
/* Headers and footers via @page (supported in Chrome/Edge) */
@page {
@top-right {
content: 'Company Ltd. — confidential';
font-size: 9pt;
color: #999;
}
@bottom-center {
content: 'Page ' counter(page) ' of ' counter(pages);
font-size: 9pt;
}
}
Tables: Repeating Header
@media print {
thead {
display: table-header-group; /* repeat header on each page */
}
tfoot {
display: table-footer-group;
}
tr {
page-break-inside: avoid;
}
table {
border-collapse: collapse;
width: 100%;
}
th, td {
border: 1px solid #999;
padding: 4pt 6pt;
text-align: left;
}
th {
background: #eee !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
Background Colors and Images
By default, browsers don't print backgrounds. For branded documents:
@media print {
.brand-block {
background-color: #1e40af !important;
color: #fff !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact; /* standard property */
}
/* Logo via img, not background-image */
.logo-bg {
background-image: none !important;
}
.logo-bg::before {
content: '';
display: block;
background-image: url('/logo-print.png');
background-size: contain;
background-repeat: no-repeat;
width: 120px;
height: 40px;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
JavaScript: Print Button and Preparation
function printPage(selector?: string) {
if (selector) {
// Print only selected area
const content = document.querySelector(selector)
if (!content) return
const printWindow = window.open('', '_blank')
if (!printWindow) return
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>${document.title}</title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body class="print-only">
${content.innerHTML}
</body>
</html>
`)
printWindow.document.close()
printWindow.focus()
printWindow.onload = () => {
printWindow.print()
printWindow.close()
}
return
}
window.print()
}
// Print button only in browser, not for screen readers
document.querySelectorAll('[data-print-btn]').forEach(btn => {
const selector = btn.getAttribute('data-print-target')
btn.addEventListener('click', () => printPage(selector ?? undefined))
})
Before/After Print Events
window.addEventListener('beforeprint', () => {
// Expand all accordions — hidden content doesn't print
document.querySelectorAll('.accordion-content[hidden]').forEach(el => {
el.removeAttribute('hidden')
el.setAttribute('data-was-hidden', 'true')
})
// Force load lazy images
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
const image = img as HTMLImageElement
if (image.dataset.src) {
image.src = image.dataset.src
}
})
})
window.addEventListener('afterprint', () => {
// Return to initial state
document.querySelectorAll('[data-was-hidden]').forEach(el => {
el.setAttribute('hidden', '')
el.removeAttribute('data-was-hidden')
})
})
Pre-Submission Checklist
Must check in Chrome → Ctrl+P: navigation is hidden, link URLs are displayed, tables don't break mid-row, images don't overflow margins, no blank first pages due to body margin-top, font is readable (not less than 10pt).
Timeline
Basic print.css for a landing or blog — 2–4 hours. For a complex page with tables, conditional visibility, and branded colors — 1 day.







