LEARNING OBJECTIVES โต
- Map and utilize the 16 margin boxes defined in the W3C CSS Paged Media Module Level 3.
- Implement dynamic page numbering using
counter(page)andcounter(pages). - Understand the
string-set()property andstring()function for dynamic running chapter titles. - Navigate the real-world rendering landscape: CSS Paged Media engines (PrinceXML, WeasyPrint) vs. Headless Chromium template headers/footers.
๐ The Mental Model & Story (Intuitive Foundation)
Pick up any published textbook or official corporate quarterly filing. Look at the perimeter outside the main reading canvas:
- At the top left of every even page: "Chapter 4: Distributed Consensus".
- At the top right of every odd page: "Section 4.2: Raft Algorithm Mechanics".
- At the bottom center or bottom right: "Page 142 of 350".
- On the very first page of the chapter: The top header is completely blank, and the page number is suppressed or rendered in Roman numerals ("Page iv").
In traditional print design, the margins are not just empty dead spaceโthey are populated by 16 specialized Margin Boxes that surround the central content area like a picture frame. The CSS Paged Media Module provides CSS at-rules that target each margin box directly and populate them with dynamic data without modifying the HTML DOM.
+------------------------------------------------------------------------------------+
| THE 16 CSS PAGE MARGIN BOXES |
+------------------------------------------------------------------------------------+
| @top-left-corner | @top-left | @top-center | @top-right | @top-right-corner |
|------------------+-------------+---------------+--------------+--------------------|
| @left-top | | @right-top |
|------------------| |--------------------|
| @left-middle | PAGE CONTENT | @right-middle |
|------------------| AREA |--------------------|
| @left-bottom | | @right-bottom |
|------------------+-------------+---------------+--------------+--------------------|
| @bottom-left-crn | @bottom-left| @bottom-center|@bottom-right | @bottom-right-crn |
+------------------------------------------------------------------------------------+
Technical Deep Dive & Specifications
1. The 16 Margin Boxes & Content Injection
Inside an @page rule, you can define margin box rules using standard CSS pseudo-declarations:
@page {
size: A4 portrait;
margin: 25mm 20mm;
/* Top Left: Organization Name */
@top-left {
content: "Acme Cloud Infrastructure Corp.";
font-family: sans-serif;
font-size: 8pt;
color: #64748b;
}
/* Top Right: Running Section Title */
@top-right {
content: "Confidential & Proprietary";
font-family: sans-serif;
font-size: 8pt;
font-weight: bold;
color: #dc2626;
}
/* Bottom Center: Page Counter */
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
font-family: sans-serif;
font-size: 9pt;
}
}
2. The Built-in CSS Page Counters
The CSS Paged Media specification specifies two native counters:
counter(page): Returns the current physical page number (1-indexed).counter(pages): Returns the total number of pages in the printed document.
/* Custom counter formatting */
@page {
@bottom-right {
/* Formats: decimal (1, 2, 3), lower-roman (i, ii, iii), upper-roman (I, II, III), lower-alpha (a, b, c) */
content: "Sheet " counter(page, upper-roman);
}
}
/* Suppress counters on the cover page */
@page :first {
@top-left { content: none; }
@top-right { content: none; }
@bottom-center { content: none; }
}
3. Dynamic Running Headers with string-set()
To pull text dynamically from the HTML content (such as the current <h2> heading) and print it inside @top-right, the specification defines string-set():
/* Capture the text content of the active h2 heading */
h2 {
string-set: chapter-title content();
}
/* Inject the captured text into the margin box */
@page {
@top-right {
content: string(chapter-title);
font-style: italic;
}
}
4. Real-World Browser Support vs. Headless PDF Engines
Understanding the browser implementation reality is crucial for senior engineers:
+-----------------------------------------------------------------------------------+
| CSS PAGED MEDIA MARGIN BOX ENGINE COMPATIBILITY |
+-----------------------------------------------------------------------------------+
Feature PrinceXML / WeasyPrint Chromium / Puppeteer / Safari
---------------------------------------------------------------------------------
@page { margin } โ
Full Support โ
Full Support
@top-left, @bottom-right โ
Full Support โ Limited / No Native CSS
counter(page), counter(pages)โ
Full Support โ (Use Puppeteer Templates)
string-set() โ
Full Support โ Not in WebKit/Blink
[!IMPORTANT]
- In Pure CSS Engines (PrinceXML, WeasyPrint, Paged.js, Typeset.sh): Margin boxes (
@top-center,@bottom-right,counter(page)) work natively via CSS.- In Chromium / Puppeteer / Playwright: You generate running headers and dynamic page counts using HTML/CSS template parameters in the PDF generation API:
page.pdf({ displayHeaderFooter: true, headerTemplate: '...', footerTemplate: '...' })(Mastered in Lesson 89.8).- In Paged.js (Polyfill for standard browsers): Translates W3C margin boxes into simulated DOM elements in standard Chrome/Firefox.
๐ป Interactive Code Playground
Below is a complete, runnable HTML document demonstrating standard CSS Paged Media margin boxes, counter formatting, and cover page suppression.
Starter Code
Line-by-Line Code Breakdown
- Lines 8โ13 (
@page): Establishes A4 geometry with $30\text{mm}$ top/bottom margins to allocate vertical breathing room for margin boxes. - Lines 16โ24 (
@top-left): Renders company branding with a subtle bottom divider line (border-bottom: 0.5pt solid #cbd5e1). - Lines 36โ43 (
@bottom-left): Injects red security classification text with a top divider line. - Lines 45โ53 (
@bottom-right): Generates dynamic page counts:"Page " counter(page) " of " counter(pages). - Lines 57โ63 (
@page :first): Suppresses headers and footers on the cover page usingcontent: none; border: none;.
Expected Browser Render Output
- Screen View: Displays three clean, stacked document pages with realistic dropshadows.
- In Paged Media Engines (PrinceXML / Paged.js / Print Preview):
- Page 1 has no running headers or footers.
- Page 2 displays top header ("ACME CORP..." and "PROJECT TITAN") and bottom footer ("CONFIDENTIAL..." and "Page 2 of 3").
- Page 3 displays identical top headers and "Page 3 of 3".
๐๏ธ Hands-On Exercise
๐ฏ The Challenge: Corporate Compliance Margin Boxes
Scenario: You are configuring an automated legal compliance report. The legal department requires:
- Standard
letter portraitwith25mmtop/bottom and20mmleft/right margins. - The
@top-centerbox must display "RESTRICTED LEGAL DISCLOSURE" in 8pt bold uppercase. - The
@bottom-rightbox must display the current page in lowercase Roman numerals (counter(page, lower-roman)). - The
@page :firstcover page must suppress all headers and footers.
๐ Starter Code Sandbox
โ ๏ธ Common Pitfalls
- Expecting
@top-centerto work natively in standard Chromewindow.print(): Standard consumer desktop Chrome does not currently implement CSS Paged Media margin boxes in its native print dialog. To render CSS margin boxes, you must use a dedicated PDF engine (PrinceXML, WeasyPrint), the Paged.js polyfill, or Puppeteer'sheaderTemplate/footerTemplateoptions. - Inadequate Margins for Margin Boxes: If you declare
@top-center { content: "Header"; }but set@page { margin-top: 5mm; }, the header text will overlap with your<body>content. Allocate at least20mmto30mmfor margins when using margin boxes. - Using JavaScript
document.write(pageNumber): JavaScript has zero awareness of physical page breaks or pagination counts in the client DOM. Never try to calculate physical page numbers using JavaScript DOM nodes; rely strictly on CSS counters or headless PDF API templates.
๐ก Pro Tips
- Use Paged.js for Browser-Based Previewing: If you want to render W3C CSS Paged Media margin boxes directly in standard web browsers, include
<script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>. It parses@pagemargin boxes and paginates standard HTML on the fly. - Leverage
counter-incrementfor Custom Section Counters: You can define custom sub-counters for appendices or figure numbers:figure { counter-increment: figure-count; } figcaption::before { content: "Figure " counter(figure-count) ": "; font-weight: bold; } - Format Page Counters for Formal Documents: Use the second argument of
counter()to adapt numbers to different document types:counter(page, lower-roman)for prefaces,counter(page, decimal)for body content, andcounter(page, upper-alpha)for appendices.
๐ Key Takeaways
- The W3C CSS Paged Media Module defines 16 distinct margin boxes surrounding the central page content area.
- Dynamic page numbering is declared via
content: counter(page)andcontent: counter(pages). - The
string-set()property captures dynamic HTML heading text and injects it into running headers withstring(). - Dedicated print engines (PrinceXML, WeasyPrint) support margin boxes natively, while Chromium pipelines use Puppeteer header/footer HTML templates.
- Always suppress headers and footers on the cover page using
@page :first { @top-left { content: none; } }. - --