Why CSS page breaks are unreliable
If you have tried generating PDFs from HTML, you have probably run into this: you addpage-break-inside: avoid or break-before: always to your CSS, and the browser ignores it. This is not a bug in your code. CSS page break properties (page-break-inside, break-before, break-after) are hints to the rendering engine, not guarantees. Chrome and other browsers frequently ignore them, especially with complex layouts, flexbox containers, or tables.
This is a fundamental limitation of the HTML-to-PDF approach. The browser lays out content for a scrollable viewport, then tries to slice it into pages after the fact. Page breaks are an afterthought, not a first-class concern.
Forme does not use CSS for layout. Every element is measured and positioned in a page-aware coordinate system. When content exceeds the page boundary, the engine splits it deterministically. There is no guessing, no hoping, and no browser-specific behavior to work around.
Automatic page breaks
When content exceeds the available space on a page, Forme automatically moves it to a new page. This happens at natural boundaries:- Between children of a View container
- Between rows of a Table
- Between lines of a Text block
Manual page breaks
Use<PageBreak /> to force content onto a new page:
breakBefore style property:
Non-breakable elements
Setwrap={false} on a View to prevent it from splitting across pages. If the element doesn’t fit on the current page, it moves entirely to the next page.
Widow and orphan control
When a text paragraph splits across pages, Forme ensures a minimum number of lines appear on each side of the break. This prevents a single isolated line at the bottom of a page (orphan) or at the top of the next page (widow). By default, at least 2 lines are kept on each side. You can adjust this per element:minOrphanLines is 2), the entire paragraph moves to the next page. If a split would leave only 1 line at the top of the next page (and minWidowLines is 2), the split point moves up to keep at least 2 lines on the next page.
Fixed headers and footers
Use<Fixed> to repeat content on every page. Fixed elements reduce the available content area.
<Page> produces.
Full example: header, footer with page numbers, and content
Watermarks
For text that appears behind content on every page, use the<Watermark> component instead of <Fixed>:
Dynamic page numbers
Use these placeholders in any<Text> element:
| Placeholder | Description |
|---|---|
{{pageNumber}} | Current page number (1-based) |
{{totalPages}} | Total number of pages in the document |
{{totalPages}} is always accurate. These are commonly used inside <Fixed> elements but work anywhere.
Table header repetition
When a table spans multiple pages, header rows (marked withheader) are automatically repeated at the top of each continuation page.
header and it repeats automatically.
Flex layout across page breaks
This is the core differentiator from other PDF tools. When a flex container splits across pages, Forme runs independent flex calculations for each page fragment. Consider a row layout with three items where the container splits after the second item:How it differs from react-pdf
react-pdf lays out content on an infinite vertical canvas and then slices it into pages. This causes several problems:- Flex breaks on page boundaries. A flex row that gets sliced has its distribution calculated for the full container, then cut in half. Both halves have wrong proportions.
- Tables break mid-row. Without page-aware row placement, a table row can be sliced between its top and bottom border.
- No header repetition. Since the table is just a set of rectangles on an infinite canvas, there is no concept of “repeat this row at the top of each page.”
- No post-split adjustment. After slicing, there is no second layout pass to fix the fragments. What you see is the result of a single layout pass on the wrong dimensions.