Someone, somewhere, is printing your docs right now. Maybe it’s a compliance auditor. Maybe it’s an on-call engineer stuck in a windowless room with a locked-down laptop and a printer that smells like 2009.
Either way, your hamburger menu and sticky cookie banner have no business being immortalized on paper.
This is a production-minded guide to print CSS for documentation and articles: readable layout, hiding navigation, wrapping code without turning it into soup, and debugging print output like you’d debug a flaky service.
What “good print” looks like (and what it refuses to do)
Print output is the harshest reviewer you’ll ever meet. It removes interactivity, shrinks your canvas to fixed dimensions, and punishes anything that depends on hover states, fixed positioning, or the user “just scrolling a bit.”
The goal isn’t to make paper look like your website. The goal is to make paper behave like paper: predictable, scannable, legible, and not full of UI detritus.
A good print stylesheet does four things well:
- Reduces noise: hides nav, search boxes, “related posts,” and anything that smells like engagement metrics.
- Improves legibility: sane font sizes, margins, line-height, and contrast.
- Respects structure: headings don’t dangle at the bottom of a page; code doesn’t get clipped; tables don’t explode.
- Preserves meaning: links show destinations; figures have captions; code wraps without lying.
Opinionated take: if your docs are operationally relevant (runbooks, incident guides, compliance procedures), print output is not a “nice to have.” It’s part of your availability story.
Joke #1: Print CSS is like backups—everyone swears they have it until they try restoring it.
One more thing: “print-friendly” does not mean “single column of tiny text.” You can use whitespace. Paper is not charged per pixel.
Interesting facts and historical context
Fact 1: CSS2 introduced the @media concept in the late 1990s, including print as a first-class target. Print support isn’t new; our follow-through is.
Fact 2: Early browser print engines often reflowed pages using different layout algorithms than screen rendering, which is why “it looks fine in the browser” was never a guarantee.
Fact 3: The @page at-rule comes from paged media requirements (think publishing). Browser support is partial, but margins are usually honored.
Fact 4: “Widows” and “orphans” are typesetting terms from the print world. CSS supports them for a reason: paper readers hate lonely lines.
Fact 5: Many corporate “print to PDF” workflows are really “headless Chromium rendering.” That means your print CSS is also your PDF export API.
Fact 6: A4 and Letter differ enough that hard-coded pixel widths fail in hilarious ways across regions. Your print layout needs real margins, not “it fits on my laptop.”
Fact 7: Some browsers intentionally omit background colors/images from printing unless the user enables it, which can erase contrast-dependent UI. Don’t rely on background colors to convey meaning.
Fact 8: Printers and PDF generators can rasterize complex pages, turning selectable text into images. Heavy effects, oversized shadows, and certain SVG filters can trigger it.
Treat these as constraints, not trivia. Constraints are where good engineering starts.
Core principles: print is a separate product
1) Start from content, not from layout
When you print, the content becomes the interface. The “design system” should mostly disappear. That means your print stylesheet should be built around semantic HTML:
headings are headings, lists are lists, code is code, tables are tables. If your site is div soup, print will punish you for your sins. It’s not personal. It’s physics.
2) Remove everything that changes state
Sticky headers, accordions, tabs, hover tooltips, cookie banners—these are stateful UI patterns. Print is stateless. If your printed doc depends on clicking “Expand,” you’ve shipped a runbook that requires electricity and optimism.
3) Make the printed artifact stand alone
Printed pages lose your domain context. The reader may not know the site name, the page title, or the last updated time. Include enough metadata in the content area—title, optional version, and maybe a small footer—but don’t rebuild the whole header.
4) Optimize for two audiences: humans and audits
Humans want legible text, wrapped code, and no wasted pages. Audits want stable artifacts. If your print output changes every time a nav link changes, you’re creating churn for no benefit.
A useful “paraphrased idea” from John Allspaw: stability comes from designing systems that assume failure will happen, then reducing the blast radius when it does.
Readable print layout: typography, margins, and structure
Margins: choose boring numbers
Use @page margins in inches or millimeters and keep them conservative. Printers have unprintable areas; PDFs don’t, but users print PDFs onto printers with unprintable areas.
If your content touches the edge, it will be clipped. Not “might.” Will.
Do: @page { margin: 0.6in; }. Don’t: margin: 0 and hope the printer is modern and benevolent.
Font sizes: print isn’t retina
For body text, 10.5–12pt is usually right. For dense technical docs, 11pt is a good default. For code, 8.5–10pt depending on line length. The moment you go smaller to “fit more,” your readers start scanning instead of reading.
Line length: the tyranny of 100 characters
Papers and PDFs don’t scroll. Keep paragraphs readable: avoid super-wide lines by using margins, not max-widths in pixels. In print, a “full width” page is already constrained. Let it breathe.
Contrast: kill the gray-on-gray aesthetic
Print styles should force high contrast. Many companies love light gray text on slightly different gray backgrounds.
Then someone prints it on a budget printer, and your “note” box becomes an invisible ghost. In print, if it’s important, it’s black.
Headings: don’t let them dangle
Use break-after: avoid (and the older page-break-after: avoid fallback) on headings. Also protect the first paragraph after a heading using a wrapper, or at least avoid having headings at the bottom of a page with no content.
Opinionated rule: if an H2 prints as the last line on a page, your print stylesheet is failing at its one job.
Hiding navigation, chrome, and UI garbage
Hide by class, not by hope
You need explicit selectors for elements that should not print. Create a utility class like .no-print and apply it to:
nav bars, footers with legal boilerplate, sidebars, “edit this page” buttons, share widgets, comment sections, and cookie banners.
Prefer “display: none” in print
For print, display: none is your friend. Don’t use opacity tricks. Don’t use off-screen positioning. Some print engines still compute layout for hidden-but-not-none elements, and you’ll get mysterious blank space.
Fixed and sticky elements are page-eaters
A fixed header that’s 72px tall becomes a repeating tax on every printed page if the browser decides to render it for each page. That’s how you turn a 10-page runbook into a 17-page runbook and make your office printer cry.
Joke #2: Nothing says “enterprise” like printing a 40-page guide where 12 pages are just the sticky header.
Links, references, and “paper reality”
Make link destinations visible
On paper, “click here” is performance art. Print styles should append the URL after external links. The classic pattern:
a[href^="http"]:after { content: " (" attr(href) ")"; }
Don’t do this for internal hash links or mailto links unless you want noise. You can selectively target external protocols.
But don’t let URLs destroy layout
Some URLs are long, and some systems generate truly heroic query strings. You need wrapping:
word-break: break-word and overflow-wrap: anywhere can help. For print, the goal is “readable enough to retype,” not “preserve the exact string on one line.”
Anchor links and TOCs
A screen TOC is useful; a printed TOC is optional. If you do print it, ensure it doesn’t steal half the first page. If your docs are long, a short TOC can help—but consider a “print view” that reorders content rather than dumping the site TOC unchanged.
Code blocks: wrapping, overflow, copy fidelity
The hard truth: code doesn’t like paper
Terminal output and code blocks were born to scroll horizontally. Paper refuses. Your choices are:
- Wrap lines and accept that copy-paste fidelity isn’t the primary goal on paper.
- Don’t wrap, but then you must ensure the content doesn’t get clipped (often impossible).
- Provide a print-safe variant (recommended): wrap by default, but allow critical command lines to be broken manually with continuation characters or explicit line breaks in the source.
Wrap pre blocks in print
For print, use white-space: pre-wrap on pre and code. Add word-break: break-word. This prevents clipping and missing characters.
Don’t use white-space: normal for code. That collapses spacing and will corrupt code examples. You want wrapping, not re-tokenization.
Preserve prompts and cues
If your docs include commands, keep prompts consistent. For print, prompts help humans understand what’s typed vs output.
But don’t make prompts huge or styled as background badges that won’t print.
Color and syntax highlighting
Many print pipelines drop background colors. Syntax highlighting can also become low contrast. In print, prefer simple:
black text, light border, maybe light gray background if you can tolerate it disappearing.
The code should still be readable without color.
Don’t let code blocks split mid-line if you can avoid it
Use break-inside: avoid on pre. It’s not perfect across all engines, but it reduces the worst cases.
For long outputs, splitting is unavoidable—so ensure your commands show context and re-entrance points.
Tables, diagrams, and images without tears
Tables: either fit or redesign
Wide tables are the number-one source of print garbage. If your table is wider than a page, you have three options:
- Make the table narrower by redesigning the content (best long-term).
- Allow the table to wrap cells aggressively (sometimes fine for prose, terrible for IDs).
- Print the table in landscape (hard in browser print; doable in dedicated PDF generation).
Don’t try to “scale down” the entire page to fit the table. That produces tiny text and angry humans.
Images: remove decorative, keep informative
Decorative images should not print. Informative images should print at high enough resolution to remain readable. Set:
img { max-width: 100%; height: auto; }
If you rely on color-coded diagrams, add labels. Printers aren’t obligated to reproduce your brand palette. They often reproduce “whatever gray this looks like today.”
SVG and filters
Complex SVG filters can trigger rasterization. When that happens, text becomes fuzzy and unselectable in the PDF.
If your docs embed SVG diagrams with blur/shadow filters, consider a print-mode fallback SVG without filters.
Page breaks, orphans, widows, and avoiding awkward splits
Use modern break properties, keep old fallbacks
Prefer break-before, break-after, and break-inside. Add older page-break-* for compatibility.
Browsers vary, but these properties are your best shot at controlling pagination.
Protect headings, short lists, and definitions
A heading at the bottom of a page is a classic failure. So is a two-item list split across pages. Apply:
h2, h3 { break-after: avoid; }pre, table, blockquote { break-inside: avoid; }li { break-inside: avoid; }(use carefully; huge lists will force awkward whitespace)
Orphans and widows: still worth using
orphans and widows are not perfectly enforced everywhere, but they help. Set them on paragraphs and list items.
You’re not trying to typeset a novel. You’re trying to stop the “single line stranded on the next page” phenomenon.
Printing to PDF in pipelines: reproducibility and drift
In many orgs, “print” really means “generate a PDF for distribution.” That’s fine—until it’s part of a compliance workflow, or a release artifact, or a support pack.
Then you discover your PDF output changes because the headless browser updated, a webfont failed to load, or a CSS rule got tree-shaken.
Pin your rendering engine
If you generate PDFs in CI, pin the Chromium version (or the container tag) and treat it like any other dependency.
Unpinned “latest” is how you get a surprise reflow the day before an audit.
Disable flaky dependencies
Webfonts can fail. Remote images can fail. External scripts can fail. For print/PDF, use local assets or inline critical CSS.
The print artifact should not depend on five CDNs and a DNS resolver having a good day.
Design for deterministic pagination
Small layout differences can change page breaks, which changes everything (page numbers referenced in SOPs, for example).
If you need deterministic pagination, consider generating PDFs from source using a paged-media toolchain or enforce stable CSS and pinned fonts.
Practical tasks: commands, outputs, decisions
Print styling fails in two ways: your CSS is wrong, or your pipeline is lying to you about what’s being printed.
Here are real tasks I use to debug print output like an SRE: verify inputs, observe behavior, and decide what to change.
Task 1: Confirm which CSS is actually shipped
cr0x@server:~$ curl -sS -D- https://docs.example.internal/article/runbook.html -o /tmp/runbook.html
HTTP/2 200
content-type: text/html; charset=utf-8
etag: "9b3b2e9a"
cache-control: max-age=300
What the output means: You got the rendered HTML and response headers. ETag and caching tell you if you might be seeing stale content.
Decision: If caching is aggressive, purge or bypass it before blaming CSS.
Task 2: Extract linked stylesheets (quick sanity check)
cr0x@server:~$ grep -Eo 'href="[^"]+\.css[^"]*"' /tmp/runbook.html | head
href="/assets/site.css"
href="/assets/print.css"
What the output means: The page includes a dedicated print CSS, not just screen CSS.
Decision: If there is no print CSS, create one. If there is, fetch it next and verify it’s the one deployed.
Task 3: Fetch print CSS and verify it contains print rules
cr0x@server:~$ curl -sS https://docs.example.internal/assets/print.css | sed -n '1,80p'
@media print {
.site-nav, .sidebar, .cookie-banner { display: none !important; }
pre { white-space: pre-wrap; word-break: break-word; }
}
What the output means: There is a @media print block and it’s doing the basics.
Decision: If print rules are missing or empty, you’re relying on screen CSS. Stop and fix that first.
Task 4: Detect accidental “print: none” on main content
cr0x@server:~$ rg -n "display:\s*none" /tmp/runbook.html /tmp/print.css
/tmp/print.css:2: .site-nav, .sidebar, .cookie-banner { display: none !important; }
What the output means: Only UI is hidden. Good.
Decision: If you find main, article, or your content wrapper hidden, fix selectors (often a too-broad rule like div or *).
Task 5: Generate a PDF via headless Chromium to reproduce issues
cr0x@server:~$ chromium --headless --disable-gpu --print-to-pdf=/tmp/runbook.pdf https://docs.example.internal/article/runbook.html
[1229/102312.184556:INFO:headless_shell.cc(659)] Written to file /tmp/runbook.pdf.
What the output means: You now have a reproducible PDF artifact from a known engine.
Decision: If the PDF differs from “Print…” in desktop Chrome, you likely have environment drift (fonts, DPI, or different Chromium versions).
Task 6: Check whether the PDF is text or rasterized images
cr0x@server:~$ pdftotext /tmp/runbook.pdf - | head
Runbook: Storage Failover
Last updated: 2025-12-01
1. Preconditions
...
What the output means: Text extraction works; the PDF is mostly real text.
Decision: If extraction yields nothing or garbage, your pipeline might be rasterizing. Reduce visual effects and check embedded fonts/SVG filters.
Task 7: Inspect embedded fonts (missing fonts cause reflow)
cr0x@server:~$ pdffonts /tmp/runbook.pdf | head
name type encoding emb sub uni object ID
AAAAAA+Inter-Regular TrueType WinAnsi yes yes yes 12 0
AAAAAA+Inter-SemiBold TrueType WinAnsi yes yes yes 13 0
What the output means: Fonts are embedded and subsetted. That’s good for portability and stable metrics.
Decision: If fonts aren’t embedded, expect layout drift across machines. Embed or use system fonts in print mode.
Task 8: Check page count and spot blank-page regressions
cr0x@server:~$ pdfinfo /tmp/runbook.pdf | egrep 'Pages|Page size'
Pages: 14
Page size: 612 x 792 pts (letter)
What the output means: You have 14 pages on Letter size. That’s a stable baseline metric to track in CI.
Decision: If pages suddenly jump (e.g., 14 to 19), suspect fixed/sticky elements, forced page breaks, or a font swap.
Task 9: Validate that long code lines wrap (not clip)
cr0x@server:~$ pdftotext /tmp/runbook.pdf - | rg -n "cr0x@server:~\$" | head
42:cr0x@server:~$ zpool status -v pool0
77:cr0x@server:~$ journalctl -u nginx --since "1 hour ago"
What the output means: Commands appear in extracted text, which implies they weren’t clipped off the page edge.
Decision: If extracted text is missing for long commands, revisit pre wrapping and margins.
Task 10: Confirm print media is applied (debugging from CSS)
cr0x@server:~$ node -e 'const fs=require("fs"); const css=fs.readFileSync("/tmp/print.css","utf8"); console.log(/@media\s+print/.test(css)?"has print media":"missing print media");'
has print media
What the output means: Your CSS includes print media rules.
Decision: If missing, your bundler might be stripping it. Fix build pipeline configuration.
Task 11: Identify unprintable “background-dependent” elements
cr0x@server:~$ rg -n "background-color|color:\s*#(7|8|9|a|b|c|d|e|f){3,6}" /tmp/print.css | head
14: .callout { border-left: 4px solid #333; background-color: #f6f6f6; }
What the output means: You’re using a background color for callouts. That’s okay, but don’t let it carry meaning.
Decision: Add borders, labels, or typography so the callout remains obvious when backgrounds don’t print.
Task 12: Check for forced page breaks that create blank pages
cr0x@server:~$ rg -n "page-break-before|page-break-after|break-before|break-after" /tmp/print.css
22: h1 { break-before: page; }
What the output means: Something forces a page break before h1. That can create a blank first page in some engines.
Decision: Remove it or scope it tightly (e.g., only in a “print packet” mode). Measure page count again.
Task 13: Verify that nav/sidebar elements actually disappear in printed PDF
cr0x@server:~$ pdftotext /tmp/runbook.pdf - | rg -n "Search|Sign in|Subscribe|Cookie" | head
What the output means: No obvious UI phrases in the printed artifact.
Decision: If UI text shows up, your selectors don’t match production DOM. Add stable classes (.site-nav, .cookie-banner) and avoid brittle CSS like nav > ul > li chains.
Task 14: Confirm the same layout renders on A4 and Letter (page-size drift check)
cr0x@server:~$ chromium --headless --disable-gpu --print-to-pdf=/tmp/runbook-a4.pdf --virtual-time-budget=10000 --run-all-compositor-stages-before-draw https://docs.example.internal/article/runbook.html
[1229/102409.918211:INFO:headless_shell.cc(659)] Written to file /tmp/runbook-a4.pdf.
What the output means: You have an additional PDF artifact to compare. (Chromium’s page size control is limited; many teams use separate tooling for A4/Letter.)
Decision: If A4 vs Letter changes cause clipped content, increase margins and stop using fixed widths.
Task 15: Catch CSS regressions in CI by pinning a page-count budget
cr0x@server:~$ pdfinfo /tmp/runbook.pdf | awk -F': *' '/Pages/ {print $2}'
14
What the output means: A single number you can compare across builds.
Decision: If page count changes unexpectedly after a CSS change, treat it like a regression and inspect the diff output, not just the HTML.
Fast diagnosis playbook
When print output is wrong, do not start by “tweaking CSS until it looks better.”
That’s how you get a thousand-line print stylesheet that no one trusts.
Diagnose like an operator: check the few things that usually break first.
First: Is the print stylesheet applied at all?
- Open print preview and verify nav/sidebar disappears.
- If not, your
@media printisn’t loading, is being stripped, or your selectors don’t match the DOM.
Second: Are you fighting fixed/sticky positioning?
- Symptoms: repeated headers, wasted whitespace, content shifted down, page count explosion.
- Fix: in print, set fixed/sticky containers to
position: static, and hide redundant chrome.
Third: Are code blocks clipping or wrapping badly?
- Symptoms: missing characters on the right edge, truncated commands, or a single code block spanning five pages with broken words.
- Fix:
pre { white-space: pre-wrap; word-break: break-word; }and ensure margins are nonzero.
Fourth: Are links meaningful on paper?
- Symptoms: “click here” everywhere, references that can’t be followed.
- Fix: append external URLs with
attr(href); avoid doing it for internal anchors.
Fifth: Are page breaks sabotaging readability?
- Symptoms: headings alone at page bottom; split tables; list items separated from their bullets.
- Fix:
break-inside: avoidon blocks;break-after: avoidon headings; tune orphans/widows.
Operational hint: snapshot a “known-good” PDF and diff it whenever you change CSS. Humans are bad at spotting subtle reflows in print preview.
Common mistakes (symptoms → root cause → fix)
1) Symptom: printed pages include nav, search, and cookie banner
Root cause: no print stylesheet, or selectors target an old DOM structure that changed during a redesign.
Fix: introduce stable classes (.site-nav, .cookie-banner, .sidebar) and hide them in @media print using display: none !important.
2) Symptom: huge blank areas, especially at top of every page
Root cause: fixed/sticky header kept in print; browser repeats it per page or reserves its space.
Fix: in print, set the header container to position: static !important or hide it entirely. Move essential metadata into the article body.
3) Symptom: code or tables are cut off on the right
Root cause: white-space: pre without wrapping, plus insufficient margins or fixed widths.
Fix: use pre-wrap for print and remove fixed widths. Increase @page margins slightly.
4) Symptom: code wraps but becomes unreadable “word salad”
Root cause: using white-space: normal or aggressive hyphenation that breaks tokens.
Fix: keep white-space: pre-wrap and prefer word-break: break-word. Avoid hyphenation for code blocks.
5) Symptom: PDF has text you can’t select, or it looks blurry
Root cause: rasterization due to heavy CSS effects, complex SVG filters, or print engine quirks.
Fix: remove shadows/filters in print, simplify SVG, and verify with pdftotext that text extraction works.
6) Symptom: headings orphaned, lists split awkwardly
Root cause: no pagination rules; relying on browser defaults; content blocks not protected from breaks.
Fix: apply break-after: avoid to headings and break-inside: avoid to critical blocks. Use widows/orphans as guardrails.
7) Symptom: “Print to PDF” differs from “Print” on desktops
Root cause: different Chromium versions, different fonts installed, different default page sizes, or missing assets in headless.
Fix: pin rendering engine versions and embed fonts (or use system fonts). Generate PDFs in a controlled container.
8) Symptom: printed pages are missing diagrams or icons
Root cause: assets blocked by CSP, cross-origin restrictions in headless mode, or user setting “don’t print backgrounds.”
Fix: don’t convey meaning with background images; inline critical SVG; ensure diagrams are real <img> or inline <svg>.
Checklists / step-by-step plan
Step-by-step: build a print stylesheet that survives production
-
Inventory what must print.
Title, last updated, headings, body text, code blocks, tables that matter, and a minimal footer (optional). Everything else starts as “no.”
-
Add
@media printwith hard resets.Force black text, remove backgrounds and shadows, normalize margins, and set a print font size.
-
Hide UI with stable classes.
Add
.no-printand apply it to nav/search/sidebar/cookie/banner/share widgets. Then hide those selectors in print. -
Fix positioning.
In print, fixed/sticky elements should become static or disappear. Test page count before and after.
-
Make links usable.
Append external URL destinations. Avoid appending for internal anchors to keep noise down.
-
Make code wrap safely.
pre-wrap+break-word+ borders. Verify long commands aren’t clipped. -
Control page breaks.
Protect headings, tables, and code blocks from splitting. Add widows/orphans defaults.
-
Test on A4 and Letter.
At least once. Your global company will thank you quietly (the highest form of gratitude).
-
Automate PDF generation in CI.
Pin the browser version. Store a baseline artifact. Alert on large diffs (page count + visual checks).
-
Write a “print acceptance test.”
A tiny checklist: nav hidden, code wraps, no clipped content, links show destinations, no blank first page.
Quick print acceptance checklist (human)
- First page starts with the title, not with empty space or a nav bar.
- No cookie banner, no search box, no “subscribe,” no chat widget.
- Code blocks wrap and remain legible; nothing clipped on the right.
- Headings aren’t separated from their first paragraph.
- Links show where they go (at least for external references).
- Tables either fit or have a clearly acceptable wrap behavior.
- The document prints on both A4 and Letter without critical loss.
Quick print acceptance checklist (CI)
- Generate PDF from a pinned headless browser.
- Extract text with
pdftotext; fail if empty. - Record page count with
pdfinfo; alert on large changes. - Optionally scan extracted text for UI strings (“Search”, “Cookie”, “Subscribe”).
Three corporate mini-stories from the print mines
Mini-story 1: the incident caused by a wrong assumption
A mid-sized SaaS company had a “print runbook” requirement for data center work: a binder of operational procedures, updated weekly.
The docs team assumed print was just “screen minus nav.” Engineering assumed the docs were “just HTML.” No one owned the PDF artifact.
Then a storage maintenance window went sideways. The on-site tech had printed runbooks because the work area had limited network access.
The runbook page for a specific failover procedure contained long commands and long device paths. On-screen it was fine: horizontal scrolling in code blocks.
Printed, the right side of every line was clipped. Not wrapped—clipped. Device IDs were missing.
The tech did what humans do under pressure: guessed. They matched the visible prefix of a device path and proceeded. The wrong target got detached.
The blast radius was contained, but it was a bad evening: avoidable downtime, a post-incident review, and a lot of “how did we not test printing?”
The root assumption was simple: “If it renders, it prints.” It doesn’t. Print is a different layout engine with different constraints.
The fix wasn’t heroic. They added a print stylesheet that wraps pre blocks, increased margins, and introduced a “print preview” CI job that generated PDFs and ran pdftotext checks.
The outcome was also simple: the next maintenance window went fine, and nobody talked about printing again—which is exactly how it should be.
Mini-story 2: the optimization that backfired
Another org decided to “optimize CSS delivery.” They extracted critical CSS for above-the-fold rendering and deferred the rest.
Print CSS was tucked into the “rest,” loaded late, because it didn’t affect first paint. On paper, that sounds reasonable.
In headless PDF generation, it was not reasonable. The PDF job loaded the page, waited for a fixed timeout, then printed.
Sometimes print CSS loaded in time. Sometimes it didn’t. The generated PDFs were nondeterministic: some had nav bars and sidebars, some didn’t.
Worse: the ones that partially loaded had both, but with broken layout. It looked like a ransom note assembled from UI fragments.
The team initially blamed the headless tool. They increased timeouts. They retried jobs. They added more CPU.
Classic move: treat a deterministic problem like a capacity problem.
The actual issue was dependency ordering. Print CSS isn’t “non-critical” if you’re generating print artifacts.
They fixed it by bundling print CSS with the main stylesheet (or at least ensuring it loaded deterministically before printing), and by waiting for a reliable “network idle” signal rather than a timeout.
Bonus lesson: when you optimize for one path (screen), you can easily harm another (print/PDF) if you don’t treat it as a first-class deliverable.
Mini-story 3: the boring but correct practice that saved the day
A financial company had an internal docs portal. They also had a habit that engineers love to mock until it saves them:
they kept “release artifacts” for operational docs. Every time a major system version shipped, a snapshot of relevant runbooks was exported to PDF and stored alongside the release package.
It was boring. It required discipline. It occasionally generated complaints because “why are we spending CI minutes printing PDFs?”
But it meant that during an incident, the on-call could pull the exact runbook version that matched the deployed system.
No guessing about whether the latest docs applied. No “the docs got updated yesterday and now the CLI flags don’t match.”
One day, their docs portal had an outage during an internal network event. Authentication issues, cascading failures, the usual corporate adventure.
Meanwhile, an unrelated service needed a recovery procedure. The runbook was in the portal. The portal was down.
The on-call reached for the release artifact PDF stored in the build system and executed the recovery steps without needing the portal.
It worked because the PDF was generated from pinned tooling, the print stylesheet was stable, and code blocks didn’t clip.
Nobody celebrated the print stylesheet. They celebrated the recovery. The print stylesheet just quietly did its job, which is the highest compliment in operations.
FAQ
1) Should I create a separate print.css file or keep print rules in the main stylesheet?
Either works. If you generate PDFs in CI, I prefer bundling print rules into the main CSS to avoid load-order issues. If you keep a separate file, ensure it loads deterministically and early enough for headless printing.
2) Why does print preview differ between Chrome and Firefox?
Different print engines, different defaults, and different levels of support for paged media features. Use print preview in both if your audience is broad, and validate with a headless pipeline if you generate PDFs.
3) How do I stop my sticky header from repeating on every printed page?
In @media print, set the sticky container to position: static !important or hide it. Also remove top padding/margins that were compensating for the header on screen.
4) Should I print the site footer with legal links and social icons?
No. If you need legal text, include a minimal print footer that’s actually relevant. Social icons on paper are just a confession that nobody thought this through.
5) What’s the best way to show URLs in print without making pages ugly?
Append URLs only for external links and keep the font smaller. If URLs are too long, allow wrapping. Don’t append for internal anchors unless you’re generating a formal report with reference IDs.
6) My code blocks wrap, but the indentation gets weird. What do I do?
Wrapping preserves whitespace with pre-wrap, but long lines will visually “hang” without indentation. Consider adding CSS for a hanging indent effect in print, or manually break long commands in the source with line continuations where appropriate.
7) Can I force landscape printing for wide tables?
Browser print support for per-page orientation is inconsistent. If you must do this reliably, use a controlled PDF generation pipeline that supports page templates and orientation rules, or redesign the table for print.
8) Why do my background colors disappear in print?
Many browsers disable background printing by default to save ink. Don’t rely on backgrounds for meaning. Use borders, labels, and high-contrast text.
9) How do I keep a table from splitting across pages?
Apply break-inside: avoid (and page-break-inside: avoid) to the table or to table rows, but know that very tall tables will still split. For critical tables, consider converting them into sections or multiple smaller tables.
10) What should I test automatically if I can only afford one print CI check?
Generate a PDF with a pinned headless browser, run pdftotext to confirm it’s not rasterized, and record page count for regression detection.
Conclusion: next steps you can do this week
Print styling is unglamorous. That’s precisely why it bites teams: nobody touches it until a customer, auditor, or on-call engineer forces the issue.
The fix is not complicated, but it does require treating print output as a real artifact with real tests.
- Create or clean up a dedicated
@media printblock that hides UI and normalizes typography. - Make code blocks wrap in print and verify long commands are not clipped.
- Append external URLs after link text so paper readers can follow references.
- Add basic pagination rules to protect headings, code, and tables from ugly splits.
- If you generate PDFs, pin the browser version and add a minimal CI check: PDF generation + text extraction + page count tracking.
Do those five things and your printed docs will stop being a liability. They won’t be beautiful. They’ll be usable. Production systems run on usable.