CSS Print Headers and Footers with Page Margin Boxes

CSS handles print headers and footers through the CSS Paged Media Module, specifically using the @page rule combined with predefined page-margin boxes. By dividing the margin area surrounding a printed page into sixteen distinct regions, CSS allows developers to position static text, dynamic page counts, and document titles directly into margins without altering the underlying HTML structure.

The @page Rule and Margin Box Layout

When formatting documents for print, the browser or rendering engine treats each sheet of paper as a paged context. The @page at-rule defines the dimensions, orientation, and margins of these pages.

The perimeter inside the page margins is divided into 16 margin boxes:

  • Top Margins: @top-left-corner, @top-left, @top-center, @top-right, @top-right-corner
  • Bottom Margins: @bottom-left-corner, @bottom-left, @bottom-center, @bottom-right, @bottom-right-corner
  • Left Margins: @left-top, @left-middle, @left-bottom
  • Right Margins: @right-top, @right-middle, @right-bottom

Headers are typically placed within @top-left, @top-center, or @top-right, while footers reside in @bottom-left, @bottom-center, or @bottom-right.

Basic Syntax and Implementation

Margin boxes function similarly to standard CSS pseudo-elements. You declare them inside an @page block and assign content using the content property.

@page {
  size: A4;
  margin: 20mm;

  @top-center {
    content: "Quarterly Performance Report";
    font-size: 9pt;
    color: #666;
  }

  @bottom-right {
    content: "Page " counter(page) " of " counter(pages);
    font-size: 9pt;
  }
}

Dynamic Page Numbering

CSS Paged Media provides built-in counters specifically for print contexts:

  • counter(page) represents the current page number.
  • counter(pages) represents the total number of pages in the printed document.

These counters update automatically per page and only function within the context of page-margin boxes or page-break events.

Extracting Document Content with Named Strings

To display dynamic document data in headers—such as the current chapter title—CSS provides the string-set property and the string() function.

  1. Identify the element containing the title in your document and copy its text into a named string using string-set.
  2. Retrieve that value inside the @page margin box using content: string(variable-name).
h1 {
  string-set: chapter-title content();
}

@page {
  @top-left {
    content: string(chapter-title);
    font-style: italic;
  }
}

When a new h1 appears in the document flow, the chapter-title value automatically updates for subsequent pages.

First, Left, and Right Page Variations

Headers and footers often need to vary depending on the page position, such as omitting headers on a cover page or alternating margins for double-sided printing. CSS accommodates this through pseudo-class page selectors:

  • @page :first targets the opening page, commonly used to clear headers (content: none).
  • @page :left targets even-numbered pages in double-sided printing.
  • @page :right targets odd-numbered pages in double-sided printing.
@page :first {
  @top-center { content: none; }
  @bottom-right { content: none; }
}

@page :left {
  @top-left { content: counter(page); }
  @top-right { content: "Document Title"; }
}

@page :right {
  @top-left { content: "Document Title"; }
  @top-right { content: counter(page); }
}

Browser Support and Print Engines

Standard web browsers have partial support for CSS Paged Media Level 3 margin boxes. While features like basic @page margins are universally supported, full margin-box placement and string-set are predominantly supported by dedicated print-to-PDF engines such as WeasyPrint, PrinceXML, and Paged.js, or Chromium-based browsers via specific print flags and headless printing pipelines.