Page numbers, headers and footers in HTML to PDF

The CSS that puts page numbers and running headers on every page, tested against the fixed-header approach.

Updated · 5 min read

Long PDFs need page numbers, and reports usually repeat their title at the top of each page. In HTML there are two ways to try it: CSS page margin boxes, and elements with position: fixed. We converted the same 4-page report both ways. Only one worked cleanly.

The short answer

@page {
  @top-left      { content: "Service pilot: final report"; font: 9pt Arial, sans-serif; color: #555; }
  @bottom-center { content: "Page " counter(page) " of " counter(pages); font: 9pt Arial, sans-serif; color: #555; }
  @bottom-right  { content: "Internal"; font: 9pt Arial, sans-serif; color: #555; }
}

Paste that into a <style> block and every page gets a title at the top and “Page n of total” at the bottom. counter(page) is the current page and counter(pages) is the total.

Two A4 PDF pages of a report. Each has a small title at the top left and Page 1 of 4 or Page 2 of 4 centred at the bottom, sitting in the page margin clear of the text.
Margin boxes: the header and page number sit in the page margin and never touch the text.

Why position: fixed headers go wrong

A common tip is to give a header position: fixed; top: 0. The converter does repeat fixed elements on every page, but they don’t take up space, so the text flows underneath them.

Two PDF pages. On page one the fixed header crowds the main heading; on page two the header text is printed on top of the first line of body text.
position: fixed: the header repeats, but on page two it is printed over the first line of text.

Padding on body only pushes content down on the first page. On later pages the text starts at the top of the page area, right where the fixed header is drawn. Margin boxes avoid this because they live in the page margin, outside the area your content flows through.

The margin boxes you can use

Each page has a ring of 16 margin boxes. The ones you’ll use most are:

  • @top-left, @top-center, @top-right
  • @bottom-left, @bottom-center, @bottom-right

The others sit in the corners and down the left and right sides. Boxes take their text from the content property: strings, counters and combinations of the two. They don’t hold HTML elements, so a logo belongs in the document itself.

Margins must be big enough

Margin boxes are drawn inside the page margin set in the converter’s settings. With the default 10 mm, one line of 9pt text fits comfortably. At 0 mm the boxes have nowhere to go and disappear; we checked, and the PDF had no page numbers at all. Use 15–20 mm if you want larger text or more breathing room. You can also set the margin in CSS with @page { margin: 15mm; }: it replaces the converter’s setting, and the boxes came back even with the setting at 0 mm.

Hide the header on the first page

Cover pages usually go without a running header. The :first page selector overrides the general rule for page one only:

@page :first {
  @top-left      { content: none; }
  @bottom-center { content: none; }
}

In our test, page one lost its header and number while pages two to four kept “Page 2 of 4” and so on. Numbering still counts the first page, so the second page is page 2.

Tips

  • Set the font in each box. Margin boxes take inherited styles from html, not body. In our test, a font set only on body didn’t reach them and they fell back to the default serif, so give each box a font, size and colour.
  • Keep the text short. Boxes on the same edge share the width, so long strings can wrap when their neighbours also have content.
  • Repeating table headings are separate. For column names on every page, use <thead>; see HTML tables to PDF.
  • Check the preview. The live preview shows one long page, so it can’t show margin boxes. Convert and check the PDF.