Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Overview
    • Why This Fork
    • Examples
    • Installing Plite
    • Adding Event Handlers
    • Defining Custom Elements
    • Applying Custom Formatting
    • Executing Commands
    • Saving to a Database
    • Canonical Change Substrate
    • Improving Performance
    • Interfaces
    • Nodes
    • Locations
    • Transforms
    • Document Changes
    • Commands
    • Editor
    • Plugins
    • Rendering
    • Serializing
    • Normalizing
    • Using TypeScript
    • Roots
    • Document State
    • Editing Behavior
    • Selection And DOM
    • Clipboard And Paste
    • Decorations, annotations, and widgets
    • Schema
    • Anchor API
    • Location API
    • Path API
    • PointEntry API
    • Point API
    • Range API
    • Selection API
    • Location Types APIs
    • Span API
    • Editor
    • Element API
    • NodeEntry API
    • Node API
    • Node Types APIs
    • Text API
    • Debug Value Scrubbing
    • Transforms API
    • Plite DOM
    • History Editor API
    • History Plugin Setup
    • History
    • Plite History
    • Plite Hyperscript
    • Plite PaginationExperimental
    • Annotations
    • DOM Coverage Boundaries
    • Editable Component
    • External Text Views
    • Plite React Event Handling
    • Virtualized Rendering
    • Plite React Hooks
    • React Editor Setup
    • React Editor
    • Plite React
    • Plite Component
    • Plite Yjs
    • Plite
    • Migration
    • Contributing
    • FAQ
    • Resources

Plite Pagination

Experimental
PreviousNext

Measure a Plite document and render one owned paged editor surface.

plitejs/pagination derives immutable page geometry from a Plite editor. PagedEditable owns the live paged surface: measurement, page arrangement, document placement, and optional page-level DOM omission.

Installation

pnpm add plitejs @chenglou/pretext react react-dom
pnpm add plitejs @chenglou/pretext react react-dom

Paged editing

Plite HyperscriptAnnotations

On This Page

InstallationPaged editingExplicit fragmentationHeadless measurementRoots and page settingsDOM coverageFidelityRelated docs
Build your editor
Production-ready AI template and reusable components.
Get all-access
import { createPretextPageLayoutEngine } from "plitejs/pagination"; import { PagedEditable, usePageLayout, } from "plitejs/pagination/react"; import { EditorRoot, useEditor } from "plitejs/react"; import { useMemo, useRef } from "react"; const DocumentEditor = () => { const editor = useEditor({ initialValue }); const editableRef = useRef<HTMLDivElement>(null); const engine = useMemo(() => createPretextPageLayoutEngine(), []); const layout = usePageLayout(editableRef); return ( <EditorRoot editor={editor}> <output>{layout?.pages.length ?? 0} pages</output> <PagedEditable ref={editableRef} engine={engine} page={{ margins: 72, preset: "letter" }} pageView={{ gap: 24, mode: "single" }} renderPage={({ attributes, page }) => ( <div {...attributes} aria-label={`Page ${page.index + 1}`} /> )} /> </EditorRoot> ); };
import { createPretextPageLayoutEngine } from "plitejs/pagination";
import {
  PagedEditable,
  usePageLayout,
} from "plitejs/pagination/react";
import { EditorRoot, useEditor } from "plitejs/react";
import { useMemo, useRef } from "react";
 
const DocumentEditor = () => {
  const editor = useEditor({ initialValue });
  const editableRef = useRef<HTMLDivElement>(null);
  const engine = useMemo(() => createPretextPageLayoutEngine(), []);
  const layout = usePageLayout(editableRef);
 
  return (
    <EditorRoot editor={editor}>
      <output>{layout?.pages.length ?? 0} pages</output>
      <PagedEditable
        ref={editableRef}
        engine={engine}
        page={{ margins: 72, preset: "letter" }}
        pageView={{ gap: 24, mode: "single" }}
        renderPage={({ attributes, page }) => (
          <div {...attributes} aria-label={`Page ${page.index + 1}`} />
        )}
      />
    </EditorRoot>
  );
};

PagedEditable accepts ordinary Editable props, including element and leaf renderers and event handlers. renderPage receives page attributes and page geometry. It renders page chrome without document children; the paged surface places editable content on the shared canvas.

pageView.mode accepts "single" and "spread". Page settings can be a PageSettings value or an EditorStateField<PageSettings>, so applications can keep shared page settings in editor state without storing derived geometry.

usePageLayout(editableRef) reads the snapshot published by that exact editable host. It returns null before the first valid layout and when measurement fails. A measurement failure reaches the editor's lifecycleErrorSink once, and again only after a measurement succeeds or the error message changes. Sibling paged surfaces keep separate snapshots.

Explicit fragmentation

Inline-flow text is the default. Non-text block structures describe their fragmentation through one provider:

<PagedEditable
  page={{ margins: 72, preset: "letter" }}
  fragmentation={({ element }) => {
    if (element.type === "image") {
      return { type: "atomic", size: { height: 320, width: 480 } };
    }
 
    if (element.type === "table") {
      return {
        type: "direct-children",
        sizes: element.children.map(() => ({ height: 36, width: 624 })),
      };
    }
 
    if (element.type === "code") {
      return { type: "text", keepTogether: true };
    }
  }}
/>
<PagedEditable
  page={{ margins: 72, preset: "letter" }}
  fragmentation={({ element }) => {
    if (element.type === "image") {
      return { type: "atomic", size: { height: 320, width: 480 } };
    }
 
    if (element.type === "table") {
      return {
        type: "direct-children",
        sizes: element.children.map(() => ({ height: 36, width: 624 })),
      };
    }
 
    if (element.type ===



  • text measures inline runs and can request soft keep-together behavior.
  • atomic places one indivisible box with an explicit size.
  • direct-children places each immediate child from its explicit size.

Plite derives all paths from the document snapshot and validates that the plan matches the node shape. Oversized content is placed once and expands occupied page geometry instead of being dropped.

Call usePageLayoutFragments() inside an element renderer to read that element's mounted page placements. This is useful when a renderer owns a fragment-aware boundary for direct children. The hook derives the current path from renderer context; callers do not pass paths.

Headless measurement

measurePages(editor, options) reads one coherent editor snapshot and returns an immutable PageLayoutSnapshot. It neither subscribes nor publishes.

import {
  createEstimatedPageLayoutEngine,
  measurePages,
} from "plitejs/pagination";
 
const snapshot = measurePages(editor, {
  engine: createEstimatedPageLayoutEngine(),
  page: { margins: 72, preset: "a4" },
});
import {
  createEstimatedPageLayoutEngine,
  measurePages,
} from "plitejs/pagination";
 
const snapshot = measurePages(editor, {
  engine: createEstimatedPageLayoutEngine(),
  page: { margins: 72, preset: "a4" },
});

Use createPretextPageLayoutEngine() for browser font measurement. Its options control whitespace, word breaking, cache size, and cold-block estimation. A custom PageLayoutEngine receives detached immutable input and must conserve source ranges, fragment order, paths, and declared non-text sizes.

Use typography.block and typography.text to match the rendered line height, spacing, font, and letter spacing. Font loading invalidates the built-in Pretext measurement cache for mounted paged surfaces.

Roots and page settings

Pass a named root to PagedEditable or measurePages to measure and render that root. Omit root for the primary document. "main" is an internal root spelling and is rejected at the public boundary.

pageSettingsCodec persists validated finite PageSettings values in a state field. The built-in presets are "a4" and "letter".

DOM coverage

PagedEditable mounts the complete paged DOM by default. Set virtualize when the complete surface exceeds the product's measured interaction or memory budget. Pagination keeps visible, selected, and explicitly materialized page content mounted in one coordinate system.

Omitted content remains in the Plite value, history, and collaboration state. Model selection and clipboard serialization can cross it. Native browser find, assistive technology, DOM printing, and third-party DOM integrations see only mounted content. Use a complete model-based export when those jobs need the whole document.

Fidelity

The Pretext engine uses browser font metrics. Font files, operating systems, and browsers can produce different line breaks. Treat live pagination as view state. Products that require identical exported pages need a controlled measurement environment for that export.

Related docs

  • Improving Performance
  • Virtualized Rendering
  • DOM Coverage Boundaries
"code"
) {
return { type: "text", keepTogether: true };
}
}}
/>