Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Page breaks

sghtmltopdf controls page breaks through the CSS Fragmentation properties.

Explicit page breaks

.chapter { break-before: page; }   /* break before this element */
.summary { break-after: page; }    /* break after this element */
.card    { break-inside: avoid; }  /* do not split this element across a page boundary */
PropertyAccepted values
break-before, break-afterauto, avoid (avoid-page and avoid-column mean the same), always (page means the same)
break-insideauto, avoid (as above)

break-before: avoid and break-after: avoid avoid a page break between sibling blocks. Even if an element fits on the page, it moves to the next page when the start of its next sibling does not fit: the first line, table row, or grid row, or the whole of an element that cannot be split. It stays put if the two together don’t fit on one page, or if it is already at the top of a page. In streaming mode, this has no effect between the direct children of <body>, because they are paginated one at a time.

The older page-break-before, page-break-after, and page-break-inside are accepted as aliases, so stylesheets written for wkhtmltopdf or wicked_pdf carry over unchanged.

The spread-related values left, right, recto, and verso, and the multi-column values, are not supported.

Without writing CSS

The same thing can be written as an HTML attribute.

<div data-page-break="before">…</div>
<div data-page-break="after">…</div>
<div data-page-break="avoid">…</div>

These count as low-priority hints, so a rule in a stylesheet can override any of them individually.

Keeping paragraphs from being split badly

p {
  orphans: 3;   /* leave at least three lines at the bottom of the page */
  widows: 3;    /* carry at least three lines to the top of the next page */
}

Both take an integer of 1 or more and default to 2. When the requirement cannot be met, the whole paragraph moves to the next page.

@page: paper and page margins

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

  @top-center    { content: "Invoice"; }
  @bottom-center { content: counter(page) " / " counter(pages); }
}

@page :first {
  @bottom-center { content: "Cover"; }
}
  • size accepts a page size keyword such as A4 or Letter, one or two <length> values, and landscape or portrait
  • @page wins over the CLI page setup options, which act as initial values
  • Text can be placed in the margin boxes, the sixteen from @top-left-corner to @bottom-right-corner, through content. Decoration such as background colours and borders is not supported there
  • counter(page) is the current page number and counter(pages) is the total page count

Limits of @page

  • size and margin cannot vary from page to page. Such declarations qualified with :first, :left, or :right are parsed but not applied; those pseudo-classes only serve to vary the contents of the margin boxes
  • Named pages, @page intro together with page: intro, are not supported
  • The margin box geometry is simplified. The four corners are fixed to where the vertical and horizontal margins meet, and the other twelve divide each edge into three equal parts; a width is ignored
  • counter(pages) can be used in streaming mode too. The margin boxes that need the total page count are drawn when the PDF is completed

CLI options such as --header-center are mapped internally onto these @page margin boxes. If you write both, the CSS wins.

Breaking tables across pages

A table that does not fit on one page is split row by row and flows across pages.

  • The rows of <thead> are repeated at the top of every page after the first. Multi-row headers work too, and nothing is repeated for a table that fits on one page
  • <tfoot> moves to the end of the table regardless of where it appears in the source, but it is not repeated at the bottom of every page; it appears once, on the last page
  • The caption goes with the first fragment for caption-side: top and with the last for bottom
  • Every fragment carries the table’s own background and borders, and border-collapse: collapse still merges borders within each page

As a known limitation, a cell whose rowspan crosses a break belongs to the fragment of its starting row and its lower part runs off the page without being clipped. Row-level break-inside: avoid and the equivalent of orphans and widows are not supported either.

How Flexbox and Grid are handled

LayoutPage breaks
display: flexAtomic when it fits (never split; moved to the next page whole). A container taller than one page is split in units of bands, groups of items that do not overlap vertically (each item of a column flex, each flex line of a wrapping row flex); a band holding a single item is split inside as well, like a block
display: gridSplit row band by row band, except at a boundary crossed by an item that spans rows
display: tableSplit row by row, as above

When laying out large cards, a flex container can jump to the next page in one piece. Use Grid or a table where you want it split.

A flex container that does not fit on one page (a document body laid out as a column flex, say) flows onto the following pages without losing the overflow. A flex line taller than one page in a row flex that does not wrap cannot be split and overflows (it is not clipped).

PDF bookmarks (outline)

An element with bookmark-level becomes an entry in the PDF’s bookmarks (outline). Entries nest by level, appear in document order, and take you to the element’s page and position when clicked. A document with no bookmark-level elements gets no bookmarks.

h1 { bookmark-level: 1; }
h2 { bookmark-level: 2; }
h3 { bookmark-level: 3; bookmark-state: closed; }  /* Open with child entries collapsed */
.appendix h1 { bookmark-label: "Appendix: " content(); }
  • An entry’s name comes from bookmark-label, which defaults to the element’s text. Text in display: none descendants is left out, and <br> and the boundaries of block-level children become spaces. Whitespace (spaces, tabs, and line breaks) collapses to one space, and no-break spaces are kept. Non-ASCII text such as Japanese works too
  • An element whose label is empty doesn’t become an entry
  • An entry becomes a child of the nearest preceding entry with a shallower level. When levels skip, such as an h3 right after an h1, it is still a child of the h1
  • An element that doesn’t fit on one page gets a single entry, on the page where it starts
  • display: none elements don’t become entries, and neither do elements inside headers and footers (--header-html, --footer-html), the cover (--cover), or the table of contents (--toc)
  • Block-level elements, table rows, and row groups (<tr>, <tbody>, and so on) are covered. An inline element such as <span> creates no entry. The property is ignored on <html> and <body>
  • It works in streaming mode too. The bookmarks are written together after the last page

Common patterns

Keep a heading from being stranded at the bottom of a page:

h2, h3 {
  break-after: avoid;   /* do not break right after a heading */
  break-inside: avoid;
}

Keep a single line item from splitting across two pages:

.line-item { break-inside: avoid; }

Always start a new page for each chapter:

section.chapter + section.chapter { break-before: page; }

Note

In streaming mode, selectors that look backwards, such as :last-child, never match. The adjacent sibling combinator + used above does work.