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

DOM Coverage Boundaries

PreviousNext

Keep hidden Plite content selectable, copyable, and materializable without mounting its DOM.

DOM coverage boundaries let Plite keep model content selectable and copyable when the matching editable DOM is intentionally not mounted. Use them for closed accordions, inactive tab panels, collapsed sections, and app-hidden element shells. They are React rendering tools; the document content stays in the Plite value, and Selection And DOM owns the broader model-selection and native-selection boundary.

Render a boundary

renderElement receives slots.contentBoundary. Wrap the hidden part of an element with a boundary and tell Plite which children it covers.

const renderElement = ({ attributes, children, element, slots }) => {
  if (element.type 




















AnnotationsEditable Component

On This Page

Render a boundaryChildren that loadSelection policyCopy policyBoundary identityBoundaries versus content roots
Build your editor
Production-ready AI template and reusable components.
Get all-access
===
"section"
) {
return (
<section {...attributes}>
{React.Children.toArray(children)[0]}
{slots.contentBoundary({
mounted: !element.collapsed,
onMaterialize: () =>
openSection(editor.key(element)),
renderPlaceholder: ({ materialize }) => (
<button onClick={materialize} type="button">
Show section
</button>
),
scope: { from: 1, type: "children" },
selectionPolicy: "materialize",
})}
</section>
);
}
return <p {...attributes}>{children}</p>;
};
const renderElement = ({ attributes, children, element, slots }) => {
  if (element.type === "section") {
    return (
      <section {...attributes}>
        {React.Children.toArray(children)[0]}
        {slots.contentBoundary({
          mounted: !element.collapsed,
          onMaterialize: () =>
            openSection(editor.key(element)),
          renderPlaceholder: ({ materialize }) => (
            <button onClick={materialize} type="button">
              Show section
            </button>
          ),
          scope: { from: 1, type: "children" },
          selectionPolicy: "materialize",
        })}
      </section>
    );
  }
 
  return <p {...attributes}>{children}</p>;
};

Use scope={{ type: 'children', from, to }} when the boundary covers child nodes. Use scope={{ type: 'self' }} when the whole element is hidden by app chrome.

A child that a renderer leaves unmounted without a boundary is a renderer bug. Plite reports it to the editor's lifecycleErrorSink with source: "render-element", as renderElement says.

Children that load

A renderer that shows its children only once something loads, such as a lazy component behind <Suspense> or an iframe it portals into, leaves them without DOM until then. Cover them with an unmounted boundary while they load, and use selectionPolicy: "model" so the selection can stay in them.

const renderElement = ({ attributes, children, element, slots }) => {
  if (element.type === "diagram") {
    return (
      <div {...attributes}>
        <React.Suspense
          fallback={slots.contentBoundary({
            mounted: false,
            renderPlaceholder: () => "Loading…",
            scope: { from: 0, to: element.children.length - 1, type: "children" },
            selectionPolicy: "model",
          })}
        >
          <DiagramFrame>{children}</DiagramFrame>
        </React.Suspense>
      </div>
    );
  }
 
  return <p {...attributes}>{children}</p>;
};
const renderElement = ({ attributes, children, element, slots }) => {
  if (element.type === "diagram") {
    return (
      <div {...attributes}>
        <React.Suspense
          fallback={slots.contentBoundary({
            mounted: false,
            renderPlaceholder: () => "Loading…",
            scope: { from: 0, to: element.children.length - 1, type: "children" },
            selectionPolicy: "model",
          })}
        >
          <






DiagramFrame is a React.lazy component. React unmounts the fallback, and the boundary with it, once the children mount.

Selection policy

selectionPolicy controls keyboard and model selection through hidden content.

PolicyBehavior
skipMove selection outside the hidden range. Use it for closed UI chrome.
modelLet the model selection include hidden content without mounting it.
materializeMount the hidden content before Plite moves selection into it.

materialize calls onMaterialize({ boundary, reason, range, rangeRole }). Use that callback to open the accordion, activate the tab, or reveal the collapsed panel.

Copy policy

copyPolicy controls clipboard output when a copied selection crosses hidden content.

PolicyBehavior
modelSerialize selected model content to plain text, HTML, and a fragment.
excludeOmit covered content from all three clipboard representations.

model is the right default for document content. exclude is usually better for app-hidden wrappers, private metadata, and chrome-only shells.

Native browser find cannot search content that is not mounted in the DOM. If a product needs Cmd+F over collapsed content, its model-search owner must reveal or otherwise present the matching content.

Screen readers also traverse mounted DOM, not hidden Plite model children. If collapsed content must remain available to assistive technology, keep an accessible summary mounted, materialize the content from the boundary, or render an app-owned accessible representation outside the editable surface.

Boundary identity

boundaryId is optional. Plite derives a stable view-local boundary id from the rendered element and scope. Pass an explicit id when tests or diagnostics need a predictable name.

Each mounted editor surface owns an independent coverage session. Two views of the same document may cover different ranges or use the same boundary id without sharing registration or cleanup.

Boundary placeholders are runtime-owned non-editable DOM. Keep tab triggers, accordion buttons, and other app chrome outside the editable text flow so native selection does not grab UI labels instead of document text.

Boundaries versus content roots

Use DOM coverage boundaries when content belongs to the current root but its DOM is hidden. Use content roots when an element owns another editable root, such as a synced block body or an editable card body.

Use External Text Views when another editing engine renders one Text in the same root. That slot owns model-copy integration and local search; it is not a void, a separate content root, or a generic virtualizer.

DiagramFrame
>{children}</
DiagramFrame
>
</React.Suspense>
</div>
);
}
return <p {...attributes}>{children}</p>;
};