From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
UNPUBLISHED
  • Overview
  • Why This Fork
  • Examples
Walkthroughs
  • Installing Plite
  • Adding Event Handlers
  • Defining Custom Elements
  • Applying Custom Formatting
  • Executing Commands
  • Saving to a Database
  • Canonical Change Substrate
  • Improving Performance
Concepts
  • Interfaces
  • Nodes
  • Locations
  • Transforms
  • Document Changes
  • Commands
  • Editor
  • Extensions
  • Rendering
  • Serializing
  • Normalizing
  • Using TypeScript
  • Roots
  • Document State
  • Editing Behavior
  • Selection And DOM
  • Clipboard And Paste
  • Projection And Overlays
  • Schema
API
  • 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
Libraries
  • Plite DOM
  • History Editor API
  • History Extension Setup
  • History
  • Plite History
  • Plite Hyperscript
  • Plite Layout
  • Annotations
  • DOM Coverage Boundaries
  • Editable Component
  • Plite React Event Handling
  • Virtualized Rendering
  • Plite React Hooks
  • React Editor Setup
  • React Editor
  • Plite React
  • Plite Component
  • Plite Yjs
  • Plite
General
  • Migration
  • Contributing
  • Docs Proof Map
  • FAQ
  • Resources

Projection And Overlays

PreviousNext

Choose between decorations, projection sources, annotations, widgets, and render props.

Projection turns model ranges into renderable text slices and overlay anchors. Use this page to choose between Editable.decorate, provider-owned decoration sources, annotations, widgets, and render props.

Choose The Right Surface

Do not put every overlay into decorate. Pick the smallest owner that matches the lifetime of the UI.

NeedStart withOwner
One editable needs a simple highlightEditable.decorate@platejs/plite-react
Search, diagnostics, or external highlights are shared with other UI
Clipboard And PasteSchema

On This Page

Choose The Right SurfaceRuntime PipelineDecorationsAnnotationsWidgetsPerformance RulesRelated Docs
Build your editor
Production-ready AI template and reusable components.
Get all-access
usePliteDecorationSource or usePliteRangeDecorationSource plus <Plite decorationSources>
@platejs/plite-react
A range has durable identityusePliteAnnotationStore plus <Plite annotationStore>@platejs/plite-react
UI is anchored to a selection, node, or annotationusePliteWidgetStore, usePliteWidgets, and usePliteWidget@platejs/plite-react
Text paint depends on projected slicesEditable renderSegment@platejs/plite-react
The overlay claim needs browser-visible proofscreenshot, DOM selection, displayed selection, and follow-up typing@platejs/browser

Use decorations for paint. Use annotations when a range needs identity. Use widgets when UI hangs off a node, selection, or annotation.

Runtime Pipeline

Projection is a React rendering layer, not a second document model.

StageWhat happensOwner
Sourcedecorate, decoration sources, annotation stores, and selection sources provide ranges.@platejs/plite-react
ProjectionSources are projected onto runtime text ids and split into slices.@platejs/plite-react
RenderEditable renders projected text through renderSegment, renderLeaf, renderText, and element renderers.@platejs/plite-react
Overlay UISidebars, popovers, toolbars, and widgets read annotation or widget snapshots.App UI plus @platejs/plite-react
Commit refreshEditor commits name the affected projection node keys when possible.@platejs/plite and @platejs/plite-react
ProofBrowser tests verify visible highlights, selection, focus, and follow-up editing.@platejs/browser

Keep document data in Plite nodes, roots, and document meta. Keep overlay payloads small and render-facing.

Decorations

Use Editable.decorate for a local highlight that belongs to one editable.

<Editable
  decorate={([node, path]) => {
    if (!TextApi.isText(node)) return [];
 
    const start = node.text.indexOf(query);
 
    return start === -1
      ? []
      : [
          {
            anchor: { path, offset: start },
            data: { search: true },
            focus: { path, offset: start + query.length },
          },
        ];
  }}
  renderSegment={(segment, children) =>
    segment.slices.some((slice) => slice.data?.search) ? (
      <mark>{children}</mark>
    ) : (
      children
    )
  }
/>
<Editable
  decorate={([node, path]) => {
    if (!TextApi.isText(node)) return [];
 
    const start = node.text.indexOf(query);
 
    return start === -1
      ? []
      : [
          {
            anchor: { path, offset: start },
            data: { search: true },
            focus: { path, offset: start + query.length },
          },
        ];
  }}
  renderSegment={(segment, children






Move to provider-owned decoration sources when ranges are shared with sidebars, toolbars, search panels, diagnostics, or other overlay UI.

const searchSource = usePliteRangeDecorationSource(editor, {
  id: "search",
  read: ({ snapshot }) => findSearchRanges(snapshot, query),
  revision: query,
});
 
return (
  <Plite decorationSources={[searchSource]} editor={editor}>
    <Editable renderSegment={renderSearchMatch} />
  </Plite>
);
const searchSource = usePliteRangeDecorationSource(editor, {
  id: "search",
  read: ({ snapshot }) => findSearchRanges(snapshot, query),
  revision: query,
});
 
return (
  <Plite decorationSources={[searchSource]} editor={editor}>
    <Editable renderSegment={renderSearchMatch} />
  </Plite>
);

Use decorateDirtiness and decorateRuntimeScope when a decoration callback depends on external projection state and can name which runtime targets should refresh.

Annotations

Annotations attach durable ids to ranges. They are the right owner for comments, suggestions, external diagnostics, and review markers.

const annotations = comments.map((comment) => ({
  anchor: comment.anchor,
  data: comment,
  id: comment.id,
  projection: { tone: comment.tone },
}));
 
const annotationStore = usePliteAnnotationStore(editor, annotations);
 
return (
  <Plite annotationStore={annotationStore} editor={editor}>
    <Editable renderSegment={renderCommentSegment} />
    <CommentsSidebar />
  </Plite>
);
const annotations = comments.map((comment) => ({
  anchor: comment.anchor,
  data: comment,
  id: comment.id,
  projection: { tone: comment.tone },
}));
 
const annotationStore = usePliteAnnotationStore(editor, annotations);
 
return (
  <Plite annotationStore={annotationStore} editor={editor}>
    <Editable renderSegment={renderCommentSegment} />
    <CommentsSidebar />
  </Plite>
);

data is app-facing metadata. projection is the small render-facing payload copied into text slices. Keep comment bodies, permissions, resolved state, and audit events in the app or sync service.

Widgets

Widgets describe UI anchored to nodes, selections, or annotations. They are useful for comment popovers, inline toolbars, floating action buttons, and side-panel rows that need resolved visibility.

const widgets = comments.map((comment) => ({
  anchor: { annotationId: comment.id, type: "annotation" },
  data: { label: comment.label },
  id: `comment-widget:${comment.id}`,
}));
 
const widgetStore = usePliteWidgetStore(editor, widgets, {
  annotationStore,
});
const widgets = comments.map((comment) => ({
  anchor: { annotationId: comment.id, type: "annotation" },
  data: { label: comment.label },
  id: `comment-widget:${comment.id}`,
}));
 
const widgetStore = usePliteWidgetStore(editor, widgets, {
  annotationStore,
});

Use usePliteWidgets(widgetStore) when a panel renders every widget. Use usePliteWidget(widgetStore, id) when one component watches one widget.

Performance Rules

Projection should reduce render work, not hide it.

RuleWhy
Hoist render callbacksStable renderElement, renderLeaf, renderText, and renderSegment props avoid avoidable text rerenders.
Prefer source-scoped refreshDecoration and annotation sources can refresh the ranges they own instead of rerunning one global decorate callback.
Keep projection payloads smallLarge objects in projection make text-slice comparison and rerender debugging harder.
Prove visible behaviorOverlay correctness needs screenshot or DOM-visible proof when the bug is visual.

Use Improving Performance for render and huge-document guidance.

Related Docs

  • Editable Component
  • Plite Component
  • Annotations
  • Plite React Hooks
  • Selection And DOM
  • Browser
)
=>
segment.slices.some((slice) => slice.data?.search) ? (
<mark>{children}</mark>
) : (
children
)
}
/>