Keep-together pagination: blocks that never split

June 10, 2026 · View on GitHub

By default a vertical section flows across page boundaries: its children fill the remaining space and continue on the next page. That is right for long prose — and wrong for a card whose heading would be orphaned from the chart or table below it.

keepTogether() makes a block paginate as a unit: when it does not fit in the remaining page space but fits on a fresh page, the whole block relocates instead of splitting.

document.pageFlow()
        .addSection("ChartCard", section -> section
                .keepTogether()
                .softPanel(DocumentColor.WHITE, 8, 16)
                .addParagraph(p -> p.text("Quarterly revenue"))
                .chart(spec))                  // heading + chart move together
        .build();

Available on:

SurfaceCallKeeps together
SectionaddSection(s -> s.keepTogether()…)any group of blocks/rows
Modulemodule(m -> m.keepTogether()…)title + body
TimelineaddTimeline(t -> t.keepTogether()…)the whole timeline
Timeline entriesaddTimeline(t -> t.keepEntriesTogether()…)each entry (marker + title + body); the timeline may still break between entries

keepEntriesTogether() is the usual choice for CV experience sections: an entry never splits mid-body, while a long history still spans pages.

Two boundaries to know:

  • Best-effort, not absolute. A block taller than a full page still flows — nothing can keep what physically cannot fit on one page.
  • Default off. Existing layouts (including the CV presets, which rely on sections flowing) are unchanged unless a block opts in.

Row, LayerStackNode, ShapeContainerNode, and CanvasLayerNode are already atomic by design and never split — keepTogether() exists for the composites (sections, modules, timelines) that flow by default.