CSS Module 23 – Debugging, performance, accessibility, tooling and print
Print stylesheets and paged media
Write print CSS that drops page chrome, prints link addresses and keeps tables whole, then control A4 pages, margins, breaks and printed colours.
What you will learn
- Write @media print rules that remove page chrome, show link addresses and keep content readable
- Set page size, margins and breaks with @page and the break properties
- Use print-color-adjust to print the colour backgrounds that carry meaning
- Predict where a long table breaks across pages, with its header repeated
Before you start
What you’ll learn · Before you start
What you will learn
- Write @media print rules that remove page chrome, show link addresses and keep content readable
- Set page size, margins and breaks with @page and the break properties
- Use print-color-adjust to print the colour backgrounds that carry meaning
- Predict where a long table breaks across pages, with its header repeated
Before you start
On this page
A print stylesheet decides what a page looks like on paper, or in a PDF saved from the print dialog. It removes
what only makes sense on a screen, such as the navigation, buttons, sidebars and cookie banners; sets readable black
text on white; prints the address after each external link, because nobody can click paper; and keeps figures and
tables from being cut in half. Paged media CSS then controls the pages themselves: @page sets the paper size
and the margins, the break- properties say where a page may or must end, and print-color-adjust decides whether
colours that carry meaning, such as a status badge, survive printing. Put the rules in a stylesheet linked with
media="print", or in an @media print block, and nothing on screen changes.
This lesson prints an article and an invoice to A4 PDFs with Chromium 153 and counts what changed, page by page, and checks the same print styles in Firefox 155.
Adding print rules, and seeing them without paper
There are two ways in, and both are standard. A separate file, <link rel="stylesheet" href="print.css" media="print">, applies only when printing, and because its media does not match a screen it never holds up the
first paint, although the browser still downloads it at a low priority. An @media print { … } block inside
an existing stylesheet keeps print rules next to the screen rules they adjust. Either way, place the print rules
after the screen rules: with equal specificity, the later declaration wins where both apply.
You do not need a printer to see the result. Chrome’s Rendering tab can emulate the print media type, Firefox’s Rules view has a print toggle and Safari’s Elements tab a Force print media styles button, all covered in the DevTools lesson. Emulation shows the print styles on screen but not the page breaks; for those, open the print preview, or save a PDF.
A print stylesheet for an article
This guide page has a site header with navigation, a sidebar, a comments box, a sticky share bar, a footer and a
fixed cookie banner. Its print stylesheet is linked with media="print":
article/article.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Choosing colours for a dashboard</title>
<link rel="stylesheet" href="screen.css">
<!-- Applies only when printing, and never holds up the first paint on screen. -->
<link rel="stylesheet" href="print.css" media="print">
</head>
<body>
<header class="site"><a class="logo" href="/">Field Notes</a>
<nav><a href="/guides/">Guides</a> <a href="/tools/">Tools</a> <a href="/about/">About</a> <button type="button">Subscribe</button></nav>
</header>
<div class="layout">
<main>
<article>
<h1>Choosing colours for a dashboard</h1>
<p class="lede">A dashboard is read in a hurry, often on a bad screen, by people who have to act on what they see. Colour can speed that up or get in the way.</p>
<h2>Start from the data, not the brand</h2>
<p>Before picking a single colour, sort what the dashboard shows into three kinds: values that are ordered, such as load from low to high; values that are categories, such as regions; and values that need attention, such as a failing check. Each kind wants a different palette, and mixing them up is the most common reason a chart is hard to read.</p>
<p>Ordered values need a sequential palette that changes mostly in lightness, so the order survives in greyscale and for readers who do not see some hues. Categories need colours that differ clearly from each other but have similar lightness, so no category looks more important than another. Alerts need one colour that is used for nothing else.</p>
<h2>Check contrast where the colours meet</h2>
<p>Text on a coloured tile needs the same contrast as text anywhere else. Lines and bars that carry meaning need 3:1 against the background, and so do the borders of controls. The <a href="https://www.w3.org/TR/WCAG22/">Web Content Accessibility Guidelines</a> state both thresholds, and a contrast checker tells you within seconds whether a pair passes.</p>
<figure>
<svg viewBox="0 0 320 120" width="320" height="120" role="img" aria-label="Five bars from light to dark blue"><rect x="10" y="70" width="50" height="40" fill="#dbeafe"/><rect x="70" y="55" width="50" height="55" fill="#93c5fd"/><rect x="130" y="40" width="50" height="70" fill="#3b82f6"/><rect x="190" y="25" width="50" height="85" fill="#1d4ed8"/><rect x="250" y="10" width="50" height="100" fill="#1e3a8a"/></svg>
<figcaption>A sequential palette: lightness carries the order.</figcaption>
</figure>
<h2>Never use colour alone</h2>
<p>Pair every colour that carries meaning with a second cue: a label, an icon, a pattern or a position. A red dot means nothing to a reader who cannot tell red from green, and nothing at all on a black-and-white printout of the weekly report.</p>
<p>The same goes for states. A selected filter can be bold as well as blue; a failing check can say "Failed" as well as turn red. The second cue costs a few pixels and saves a support call.</p>
<h2>Test the palette in the conditions people use</h2>
<p>Look at the dashboard on a cheap laptop screen at an angle, on a phone in daylight and printed in greyscale. Run it through a colour blindness simulation and through forced colours mode. Anything that only works on a calibrated monitor in a dark room will fail someone.</p>
<table>
<caption>Palette checks before release</caption>
<thead><tr><th>Check</th><th>Tool</th><th>Pass when</th></tr></thead>
<tbody>
<tr><td>Text contrast</td><td>Contrast checker</td><td>4.5:1, or 3:1 for large text</td></tr>
<tr><td>Graphics contrast</td><td>Contrast checker</td><td>3:1 against the background</td></tr>
<tr><td>Colour vision</td><td>Simulator</td><td>Every category still distinct</td></tr>
<tr><td>Greyscale print</td><td>Printer</td><td>Order and alerts still readable</td></tr>
</tbody>
</table>
<h2>Write the rules down</h2>
<p>Put the palettes, their purposes and their contrast results in the design system, next to the components that use them. A palette without a written purpose drifts within months, as each new chart borrows whichever colour looks nice. More on that in the <a href="https://example.com/guides/design-tokens/">guide to design tokens</a>.</p>
</article>
<section class="comments"><h2>Comments</h2><p>Sign in to join the discussion.</p></section>
</main>
<aside class="sidebar"><h2>Related guides</h2><ul><li><a href="/guides/charts/">Picking a chart type</a></li><li><a href="/guides/tables/">Tables that scan</a></li><li><a href="/guides/legends/">Legends people read</a></li></ul></aside>
</div>
<div class="share">Share: <button type="button">Copy link</button> <button type="button">Email</button></div>
<footer class="site-footer"><p>Field Notes · Guides for people who build interfaces</p><p><a href="/privacy/">Privacy</a> · <a href="/contact/">Contact</a></p></footer>
<div class="cookie">This site uses one cookie to remember your theme. <button type="button">OK</button></div>
</body>
</html> article/screen.css
/* The page's normal styles: they apply on screen and, unless print.css overrides them, in print too. */
body { margin: 0; font: 17px/1.6 system-ui, sans-serif; color: #1f2937; background: #f8fafc; }
.site { display: flex; justify-content: space-between; align-items: center; padding: 12px 24px; background: #0f172a; color: #fff; }
.site a { color: #fff; }
.layout { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 32px; max-inline-size: 1100px; margin: 24px auto; padding-inline: 24px; }
.sidebar { padding: 16px; background: #e2e8f0; border-radius: 8px; }
.lede { font-size: 1.2em; color: #334155; }
figure { margin: 24px 0; }
table { border-collapse: collapse; inline-size: 100%; }
th, td { padding: 8px; border-block-end: 1px solid #cbd5e1; text-align: start; }
.comments { margin-block-start: 48px; padding: 16px; background: #fff; }
.share { position: sticky; inset-block-end: 0; padding: 12px 24px; background: #fff; border-block-start: 1px solid #cbd5e1; }
.site-footer { padding: 24px; background: #0f172a; color: #cbd5e1; }
.site-footer a { color: #fff; }
.cookie { position: fixed; inset: auto 16px 16px; padding: 16px; background: #1e293b; color: #fff; border-radius: 8px; } article/print.css
/* The print stylesheet. It is linked with media="print", so none of it applies on screen. */
@page {
size: A4;
margin: 18mm 16mm;
}
/* Page chrome that means nothing on paper. */
.site nav, .sidebar, .comments, .share, .site-footer, .cookie { display: none; }
/* One column, black on white, a size that reads well on paper. */
body { font: 11pt/1.5 Georgia, "Times New Roman", serif; color: #000; background: none; }
.site { padding: 0 0 6mm; background: none; color: #000; }
.site a { color: #000; text-decoration: none; }
.layout { display: block; max-inline-size: none; margin: 0; padding: 0; }
/* Links: keep them recognisable, and print the address of every external one after its text. */
a { color: inherit; }
article a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; overflow-wrap: anywhere; }
/* Keep figures and tables whole, and headings with what follows them where the browser can. */
figure, table { break-inside: avoid; }
h2 { break-after: avoid; }
p { orphans: 3; widows: 3; } Printed to A4 by Chromium, with 10 mm margins when the page set none:
| Without print.css | With print.css | |
|---|---|---|
| Pages | 3 | 2 |
| Navigation, sidebar, comments, footer | printed | gone |
| The fixed cookie banner | printed on every page | gone |
| The table and its caption | caption at the foot of page 2, table on page 3 | together on page 2 |
| The link to the WCAG 2.2 standard | printed as plain link text | followed by its address in brackets |
| Text colour | the screen’s greys and link blue | black |
Two of those results are typical of printing without a print stylesheet. A position: fixed element belongs to the
viewport, and on paper each page is a viewport, so the cookie banner appeared at the bottom of all three pages. And
the table’s caption stayed behind when the table moved on, because nothing asked the browser to keep them together;
break-inside: avoid on the table did. The link addresses come from one rule:
a[href^="http"]::after { content: " (" attr(href) ")"; } prints each external address after the link text, and
overflow-wrap: anywhere stops a long address from running off the page. Firefox applied the same rules under its
print emulation: the sidebar was hidden, the body font was Georgia, and the generated address was there.
Pages: size and margins with @page
@page sets up the page box. size: A4 (or A5, letter, A4 landscape, or two lengths) chooses the paper, and
margin the space around the content, best given in millimetres or inches because paper is measured that way. A
CSS inch is 96 px, so the content area of an A4 page with 14 mm margins is 269 mm, about 1,016 CSS px tall.
| Feature | What it does | Support |
|---|---|---|
@page { size; margin } |
Paper size and margins | Baseline newly available: Safari added size last |
@page :first |
Different margins for the first page | Baseline widely available |
@page :left, :right |
Different margins for facing pages | Chrome and Safari, not Firefox |
page: wide and @page wide { … } |
Named pages, for example a landscape page for a wide table | The page property is widely available, @page itself newly available |
Margin boxes such as @top-center |
Running headers, page numbers | Chromium only |
The print dialog still has the last word: people can choose another paper size, scale the page or change the margins, so a print stylesheet should survive A4 and US letter alike.
Breaks: where a page may end
The break properties say what may happen between and inside boxes. break-before: page and break-after: page
force a new page, for example before each chapter. break-inside: avoid asks the browser not to split a box, such as
a figure, a table row or a totals block, if it can avoid it. Their support differs by value:
| Declaration | Use it for | Support |
|---|---|---|
break-before: page, break-after: page |
Start each section on a new page | Baseline widely available |
break-inside: avoid |
Figures, table rows, cards, a totals block | Baseline widely available |
break-after: avoid |
Keep a heading with the paragraph after it | Chromium only |
orphans: 3, widows: 3 |
At least three lines of a paragraph at the foot and the top of a page | Chrome and Safari, not Firefox |
The older page-break-before, page-break-after and page-break-inside are kept as legacy aliases of the
break- properties: they still work, but new code uses the new names. For tables, a <thead> is displayed as a
table-header-group, and CSS 2 allows printing user agents to repeat such header rows on every page a table spans;
Chromium did that for the invoice below.
How the 40-row invoice broke across two printed pages
Text description of the diagram
The diagram shows two pages, one under the other.
Page 1, A4 with 14 mm margins, holds from top to bottom: the brand band with the Paid badge, printed in colour because of print-color-adjust: exact; the From and Bill to addresses; the column headings; and rows 1 to 21, only whole rows because of break-inside: avoid.
An arrow labelled "the next row did not fit" leads to page 2, which holds: the column headings again, because the table's thead is a table-header-group that repeats; rows 22 to 40; and the subtotal, tax and total, kept together in one piece.
Colours that must print
Browsers save ink by default. Unless the person printing ticks an option such as “Background graphics”, background
colours and images are left out, and the CSS Color Adjustment specification lets the browser adjust text colours
too. That is the default value, print-color-adjust: economy. print-color-adjust: exact marks an element whose
colours carry meaning and asks the browser to print them as they are. It is Baseline newly available.
The invoice below has a teal brand band with white text and a green “Paid” badge. Printed with backgrounds off, which is how the dialog usually starts:
| Print rules | Brand band and badge | Their white text |
|---|---|---|
| no print stylesheet | not painted | printed light grey (#ababab) on white |
print stylesheet without print-color-adjust |
not painted | light grey on white |
print stylesheet with print-color-adjust: exact |
teal and green | white, readable |
Without exact, the status that the badge exists to show is gone from the page. Use exact only for such
elements, never for the whole page: a large printed background costs the reader ink and does not help them read.
A worked example: an invoice on one A4 page
The invoice sits inside a billing app: a toolbar with Send, Download and Print buttons, a navigation column and a help
footer. Its print rules are in a separate stylesheet, inside @media print:
invoice/invoice.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Invoice NL-2417</title>
<link rel="stylesheet" href="invoice.css">
<link rel="stylesheet" href="invoice-print.css">
</head>
<body>
<header class="toolbar">Billing <span class="actions"><button type="button">Send</button> <button type="button">Download</button> <button type="button">Print</button></span></header>
<nav class="side"><a href="#invoices">Invoices</a> <a href="#customers">Customers</a> <a href="#settings">Settings</a></nav>
<main class="sheet">
<div class="brand"><strong>Nilgiri Leaf Company</strong> <span class="status">Paid</span></div>
<h1>Tax invoice NL-2417</h1>
<div class="parties">
<p><b>From</b><br>Nilgiri Leaf Company<br>14 Hill Road, Coonoor 643101</p>
<p><b>Bill to</b><br>Meera Iyer<br>22 Lake View, Bengaluru 560034</p>
</div>
<table class="items">
<thead><tr><th>Description</th><th class="num">Qty</th><th class="num">Rate</th><th class="num">Amount</th></tr></thead>
<tbody>
<tr><td>Nilgiri green tea, 250 g</td><td class="num">4</td><td class="num">₹450.00</td><td class="num">₹1,800.00</td></tr>
<tr><td>Assam breakfast tea, 500 g</td><td class="num">2</td><td class="num">₹620.00</td><td class="num">₹1,240.00</td></tr>
<tr><td>Darjeeling first flush, 100 g</td><td class="num">3</td><td class="num">₹780.00</td><td class="num">₹2,340.00</td></tr>
<tr><td>Masala chai blend, 250 g</td><td class="num">6</td><td class="num">₹340.00</td><td class="num">₹2,040.00</td></tr>
<tr><td>Lemongrass infusion, 100 g</td><td class="num">5</td><td class="num">₹210.00</td><td class="num">₹1,050.00</td></tr>
<tr><td>Tulsi tea bags, 25 bags</td><td class="num">8</td><td class="num">₹160.00</td><td class="num">₹1,280.00</td></tr>
<tr><td>Kashmiri kahwa, 100 g</td><td class="num">2</td><td class="num">₹540.00</td><td class="num">₹1,080.00</td></tr>
<tr><td>Glass tea pot, 600 ml</td><td class="num">1</td><td class="num">₹1,450.00</td><td class="num">₹1,450.00</td></tr>
<tr><td>Steel strainer</td><td class="num">4</td><td class="num">₹120.00</td><td class="num">₹480.00</td></tr>
<tr><td>Clay cups, set of 6</td><td class="num">2</td><td class="num">₹480.00</td><td class="num">₹960.00</td></tr>
<tr><td>Gift box, large</td><td class="num">3</td><td class="num">₹260.00</td><td class="num">₹780.00</td></tr>
<tr><td>Shipping</td><td class="num">1</td><td class="num">₹150.00</td><td class="num">₹150.00</td></tr>
</tbody>
</table>
<div class="totals">
<p>Subtotal <span>₹14,650.00</span></p>
<p>Tax (5 %) <span>₹732.50</span></p>
<p class="grand">Total <span>₹15,382.50</span></p>
</div>
<p class="note">This is a made-up invoice for a lesson on print layout. It shows layout, not tax rules.</p>
</main>
<footer class="app-footer">Need help? Open the help centre.</footer>
</body>
</html> invoice/invoice.css
/* The billing app's screen styles. */
body { display: grid; grid-template-columns: 180px 1fr; grid-template-rows: auto 1fr auto; margin: 0; min-block-size: 100vh; font: 15px/1.5 system-ui, sans-serif; background: #f1f5f9; color: #0f172a; }
.toolbar { grid-column: 1 / -1; display: flex; justify-content: space-between; padding: 12px 20px; background: #0f172a; color: #fff; }
.side { display: flex; flex-direction: column; gap: 8px; padding: 20px; background: #e2e8f0; }
.sheet { max-inline-size: 760px; margin: 24px; padding: 32px; background: #fff; box-shadow: 0 2px 12px rgb(15 23 42 / 12%); }
.brand { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: #0f766e; color: #fff; }
.status { padding: 2px 10px; border-radius: 999px; background: #16a34a; color: #fff; font-weight: 700; }
.parties { display: flex; justify-content: space-between; gap: 24px; }
.items { inline-size: 100%; border-collapse: collapse; }
.items th, .items td { padding: 6px 8px; border-block-end: 1px solid #cbd5e1; text-align: start; }
.items th { background: #f1f5f9; }
.num { text-align: end; font-variant-numeric: tabular-nums; }
.totals { margin-inline-start: auto; inline-size: 260px; }
.totals p { display: flex; justify-content: space-between; margin: 4px 0; }
.grand { font-weight: 700; border-block-start: 2px solid #0f172a; padding-block-start: 4px; }
.note { color: #475569; font-size: 13px; }
.app-footer { grid-column: 1 / -1; padding: 12px 20px; background: #e2e8f0; } invoice/invoice-print.css
/* What changes on paper. All of it sits in @media print, so the screen is unaffected. */
@media print {
@page { size: A4; margin: 14mm; }
/* The app around the invoice: not part of the document. */
.toolbar, .side, .app-footer { display: none; }
body { display: block; min-block-size: 0; background: none; }
.sheet { max-inline-size: none; margin: 0; padding: 0; box-shadow: none; }
/* The brand band and the status badge carry meaning: print their colours even when the browser drops backgrounds. */
.brand, .status { print-color-adjust: exact; }
/* Long invoices: the column headings repeat on every page, rows never split, totals stay in one piece. */
.items thead { display: table-header-group; }
.items tr, .totals { break-inside: avoid; }
} India-specific
The sample is a made-up tax invoice in rupees, with amounts in the Indian digit grouping (₹15,382.50) and the “From” and “Bill to” blocks of a GST-style layout. It demonstrates print layout only; it is not a guide to what a GST invoice must contain.
| Without invoice-print.css | With it | |
|---|---|---|
| Pages for 12 items | 2: the invoice, then a page holding only the app’s help line | 1 |
| Toolbar, navigation, help footer | printed | gone |
| Brand band and Paid badge, backgrounds off | not painted | printed in colour |
| The same invoice with 40 items | — | 2 pages: rows 1 to 21, then the headings again with rows 22 to 40 and the totals |
The second page of the 40-row invoice began with the column headings, repeated by the browser, and the subtotal,
tax and total stayed together after the last row. In Firefox, under print emulation, the same rules applied: the
toolbar was hidden, the badge had print-color-adjust: exact, the <thead> was a table-header-group and the rows
had break-inside: avoid.
Practise: break a long table into pages
Exercise · Medium · JavaScript
Work out where a long invoice table breaks across pages
When a long table is printed, the browser fills each page with whole rows, repeats the table's header at the top of every page and moves anything that must not be split to the next page. Write that calculation in pages.mjs, as two exported functions.
1. contentHeight(paper, margins) returns the height of a page's content area in CSS pixels, rounded down to a whole pixel. paper is 'A4' (297 mm tall), 'A5' (210 mm) or 'letter' (279.4 mm), and margins is { top, bottom } in millimetres. A CSS inch is 96 px and 25.4 mm. 2. paginate({ pageHeight, headerHeight, rows, firstPageOffset, totalsHeight }) returns the pages as a list, each page a list of the indexes of its rows. All heights are in px; firstPageOffset and totalsHeight default to 0. - Every page that has rows starts with the table's header, headerHeight tall. The first page also holds firstPageOffset px of other content above the table. - Rows are placed in order and never split. A row that does not fit in what is left of the page starts a new page, unless the page has no rows yet: a row taller than a whole page then gets that page to itself. - The totals block, totalsHeight tall, comes after the last row and stays on the same page. When it does not fit after the last row, the last row moves to a new page with it, unless it is the only row on its page. - A table without rows has no pages: return [].
The sample tests import both functions from pages.mjs and run in your browser.
Starter code · pages.mjs
/** The height of a page's content area in CSS px: 96 px to the inch, 25.4 mm to the inch, rounded down. */
export function contentHeight(paper, margins) {
// Replace this line with your code.
return 0;
}
/** Which rows of a long table go on which page: a list of pages, each a list of row indexes. */
export function paginate({ pageHeight, headerHeight, rows, firstPageOffset = 0, totalsHeight = 0 }) {
// Replace this line with your code.
return [];
} The sample tests · pages.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { contentHeight, paginate } from './pages.mjs';
const range = (from, to) => Array.from({ length: to - from + 1 }, (_, i) => from + i);
const rows = (count, height) => Array(count).fill(height);
test('content height of a page, from paper and margins', () => {
assert.equal(contentHeight('A4', { top: 14, bottom: 14 }), 1016);
assert.equal(contentHeight('A5', { top: 10, bottom: 10 }), 718);
assert.equal(contentHeight('letter', { top: 12.7, bottom: 12.7 }), 960);
});
test('the header repeats, and the first page has less room', () => {
const pages = paginate({ pageHeight: 1000, headerHeight: 40, rows: rows(30, 36), firstPageOffset: 300 });
assert.deepEqual(pages, [range(0, 17), range(18, 29)]);
});
test('rows of different heights never split', () => {
const pages = paginate({ pageHeight: 400, headerHeight: 40, rows: [100, 120, 150, 90, 200] });
assert.deepEqual(pages, [[0, 1], [2, 3], [4]]);
});
test('the totals stay with the last row', () => {
assert.deepEqual(paginate({ pageHeight: 1000, headerHeight: 40, rows: rows(30, 36), firstPageOffset: 300, totalsHeight: 120 }), [range(0, 17), range(18, 29)]);
assert.deepEqual(paginate({ pageHeight: 1000, headerHeight: 40, rows: rows(44, 36), firstPageOffset: 300, totalsHeight: 120 }), [range(0, 17), range(18, 42), [43]]);
});
test('a row taller than a page gets a page of its own', () => {
assert.deepEqual(paginate({ pageHeight: 300, headerHeight: 40, rows: [100, 400, 100] }), [[0], [1], [2]]);
});
test('edge cases', () => {
assert.deepEqual(paginate({ pageHeight: 300, headerHeight: 40, rows: [] }), []);
assert.deepEqual(paginate({ pageHeight: 300, headerHeight: 40, rows: [200], totalsHeight: 100 }), [[0]]);
assert.deepEqual(paginate({ pageHeight: 300, headerHeight: 40, rows: [130, 130] }), [[0, 1]]);
}); A hint
Keep a running total of the height used on the current page, starting at firstPageOffset + headerHeight. Before placing a row, check whether it still fits; when it does not and the page already has rows, close the page and start a new one at headerHeight. Deal with the totals once, after the last row has been placed.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Fresher · What does a print stylesheet usually change, and how can you check it without printing?
It removes what only works on a screen: the navigation, buttons, sidebars, sticky bars and cookie banners, usually
with display: none. It sets dark text on a white background in a size that reads well on paper, prints the address
after external links with a[href^="http"]::after { content: " (" attr(href) ")"; }, and keeps figures, table rows
and similar boxes whole with break-inside: avoid. @page sets the paper size and margins. To check it, emulate the
print media type in DevTools (Chrome’s Rendering tab, Firefox’s print toggle in the Rules view, Safari’s Force print
media styles button) to see the styles, then use the browser’s print preview or save a PDF to see where the pages
break. Emulation alone does not show page breaks.
Key takeaways
- Link print rules with
media="print"or wrap them in@media print; neither changes the screen. - Hide the page chrome, and fixed elements in particular: a fixed cookie banner printed on every page.
- Print external link addresses with
attr(href)in::after, and let long addresses wrap. @page { size: A4; margin: 14mm; }sets the paper and margins;:firstand named pages are widely available,:left,:rightand margin boxes are not.break-inside: avoidkeeps figures, rows and totals whole;break-before: pagestarts a new page;theadrepeats on every page a table spans.- Backgrounds are dropped by default;
print-color-adjust: exactkeeps the colours that carry meaning.
References
- CSS Paged Media Module Level 3: the @page rule (W3C CSS Working Group)
- CSS Paged Media Module Level 3: page size (W3C CSS Working Group)
- CSS Paged Media Module Level 3: page selectors (W3C CSS Working Group)
- CSS Fragmentation Module Level 3: breaks between and within boxes (W3C CSS Working Group)
- CSS Fragmentation Module Level 3: the page-break aliases (W3C CSS Working Group)
- CSS Fragmentation Module Level 3: widows and orphans (W3C CSS Working Group)
- CSS Color Adjustment Module Level 1: print-color-adjust (W3C CSS Working Group)
- CSS 2: table display and repeated header rows (W3C CSS Working Group)
- CSS 2: the content property and attr() (W3C CSS Working Group)
- @page (MDN Web Docs) (Mozilla)
- Chrome DevTools: emulate CSS media type (print) (Google)
- Firefox DevTools: view @media rules for print (Mozilla)
- Web features explorer: page setup (W3C WebDX Community Group)
- Web features explorer: page breaks (W3C WebDX Community Group)
- Web features explorer: print-color-adjust (W3C WebDX Community Group)
- Playwright: page.pdf() (Microsoft)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress