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:
{{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:A row that crosses a page keeps its columns
AflexDirection: 'row' taller than the space left on the page continues as parallel columns on every page it spans. Each column carries on beneath itself on the next page, in its own track:
flexWrap: 'wrap') as well, since 0.24.0.
Three details worth knowing:
- A row that does not fit starts anyway. It begins in the space that is there and continues overleaf, rather than moving to the next page whole and abandoning what was left. Each column’s own widow and orphan control decides whether its first line is worth placing, so a column with room for one stranded line pushes that line instead.
alignItems: 'stretch'continues the band. A column whose content ends before its neighbour’s still paints its background and borders down every page the row crosses, the way a browser does, rather than stopping where its text ran out.- A fixed
heightis honoured across the boundary. A box withheight: 500that has only 130pt of page left paints 130pt on that page and the remaining 370 on the next, instead of shrinking to its content.
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.