Current status

Headers and footers go in native @page margin boxes (see below), including @page :first/ :left/:right overrides, with page numbers via counter(page). CSS fixed positioning (position: fixed) repeats content on every page inside the page area, for overlays such as a corner badge. Tables spanning multiple pages continue correctly, and the <thead> row repeats at the top of every continuation page automatically.

CSS fixed positioning

Use position: fixed to place content at a fixed position on every rendered page. As in Chrome (CSS 2.1 §10.1), its containing block is the page area inside the @page margins: top: 0 / bottom: 0 sit on the content area's edges, and anything outside the page area is clipped. To draw in the margins, use the @page margin boxes (@top-center, @bottom-right, ...) or the fluent Header()/Footer(), which are placed against the whole sheet. (Before 2.0, EggPdf placed author fixed boxes against the whole sheet.)

⚠️
A fixed box sits on top of the content flow inside the page area, so it doesn't reserve any space. For a header or footer that needs its own band, use the @page margin boxes below (sized by @page margin-top/margin-bottom) or the fluent Header()/Footer().
<style>
.badge {
    position: fixed;
    top: 0; right: 0;   /* top-right corner of the page area */
    font-size: 9px;
    color: #b91c1c;
}
</style>

<div class="badge">CONFIDENTIAL</div>

Page numbers with counter(page) (works today)

counter(page) and counter(pages) resolve per physical page inside a position: fixed element's content and in margin boxes — put them in a ::after (or ::before) rule rather than as literal element text, since a real page number isn't known until the whole document is paginated:

.badge::after {
    content: " · page " counter(page) " of " counter(pages);
}

Each physical page gets its own resolved number — no manual per-page text needed.

Large tables spanning pages (works today)

A <table> taller than one physical page correctly continues onto as many additional pages as it needs — no rows are lost or painted off-page, regardless of table size:

<table>
  <thead>
    <tr>
      <th>Invoice #</th>
      <th>Date</th>
      <th>Amount</th>
    </tr>
  </thead>
  <tbody>
    <!-- rows continue onto additional pages as needed -->
  </tbody>
</table>

The <thead> row itself repeats at the top of every continuation page automatically — no extra markup needed.

@page margin boxes

Headers and footers can also use standard CSS @page margin box syntax — the same as Chrome's print stylesheet support. All 16 margin-box positions are supported (@top-left/-center/-right, @bottom-left/-center/-right, the four -corner boxes, and the six @left-*/@right-* side boxes), with content (literal strings, counter(page), counter(pages), attr()) plus basic font-size, color, font-family and font-weight styling:

@page {
    margin: 2cm 2cm 3cm 2cm; /* extra bottom space for footer */

    @top-center {
        content: "My Document";
        font-size: 9pt;
        color: #666;
    }
    @bottom-center {
        content: "Page " counter(page) " of " counter(pages);
        font-size: 8pt;
    }
}
@page :first {
    @top-center { content: none; } /* no header on the first page */
}

@page :first, :left and :right page-selector scoping is also supported, for margin-box content only: :first overrides apply to physical page 1, :left/:right alternate on even/odd pages after that (a margin box with no override for the current page type falls back to the base @page rule). Page size and margin declarations on a selector-scoped rule are not applied — this engine lays out in a single pass, so a different page size per page type isn't supported yet.