Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Introduction
  • Installation
    • Plate UI
      • Next.js
      • React
    • Manual
    • RSC
    • Node.js
    • Local Docs
    • MCP
  • Releases

Selection

PreviousNext

Read, update, focus, and render text and node selections in Plate.

Plate stores selection in the editor model and projects text selection to the browser through the mounted editor. This guide covers model reads and updates, focus, rendering, and the browser boundary.

Ownership

The editor model is the single selection state. Each rendering layer derives its presentation from that state.

JobOwner
Selection state, mapping, validation, reads, and updatesplatejs
Native DOM selection, focus synchronization, and inactive paintEditorContent
Exact node-selection highlighting and pointer-drag input and from
EditorDocument Model

On This Page

OwnershipQuick startFocus and native selectionInactive selectionMark a focus targetStyle the paintFocus behaviorNode selection renderingRelated selection featuresAPI ReferenceDOM reconciliationHidden and external contentVerify selection behaviorKeyboard-selectable content
Build your editor
Production-ready AI template and reusable components.
Get all-access
NodeSelectionHighlight
NodeSelectionDrag
platejs/react
Focus markers, colors, borders, and stackingCopied registry UI

Quick start

editor.read.selection() returns the active representative Range or null. For an exact node selection, use selection.nodes() or selection.ranges() instead of treating that representative range as exact membership.

selection.ts
const range = editor.read.selection();
const expanded = editor.read.selection.isExpanded();
const selectedNodes = editor.read.selection.nodes();
const exactRanges = editor.read.selection.ranges();
 
editor.update.selection.set({
  anchor: { path: [0, 0], offset: 0 },
  focus: { path: [0, 0], offset: 5 },
});
 
editor.update.selection.setNodes([[0], [2]]);
editor.update.selection.collapse({ edge: 'end' });
editor.update.selection.set(null);
selection.ts
const range = editor.read.selection();
const expanded = editor.read.selection.isExpanded();
const selectedNodes = editor.read.selection.nodes();
const exactRanges = editor.read.selection.ranges();
 
editor.update.selection.set({
  anchor: { path: [0, 0], offset: 0 },
  focus: { path: [0, 0], offset: 5 },
});
 
editor.update.selection.setNodes([[0], [2]]);
editor.update.selection.collapse({ edge: 'end' });
editor.update.selection.set
Model stateselection()Exact membershipBrowser presentation
Text selectionOne Rangeranges() returns that rangeNative browser selection while the mounted editor is focused
Node selectionOne representative Rangenodes() and ranges() return every selected nodePlate node-selection primitives; the native selection stays empty
No selectionnullEmpty arraysNone

Use Editor Methods for editor access inside React and Selection API for the complete query and validation contracts.

Focus and native selection

Updating the model selection does not require direct DOM selection writes. When an action should return input to the editor, set the selection and focus the mounted editor explicitly:

editor.update.selection.set(range);
editor.api.dom.focus();
editor.update.selection.set(range);
editor.api.dom.focus();

Mouse, keyboard, touch, composition, and native input can move the browser selection. The mounted editor imports those changes into the model and exports model updates back to the DOM. Application code should not patch window.getSelection() for normal editor behavior.

Inactive selection

Loading…

Plate EditorContent renders an inactive selection when focus moves to a marked control. The behavior is built in; it does not require a prop, plugin, kit, or copied selection state.

Mark a focus target

Add data-editor-keep-selection-visible to a focusable control or any composed ancestor:

<div data-editor-keep-selection-visible="">
  <button type="button">Edit link</button>
</div>
<div data-editor-keep-selection-visible="">
  <button type="button">Edit link</button>
</div>

The exact EditorContent that loses focus reads its live canonical selection. An expanded range renders a highlight. A collapsed range renders a caret. Focus returning to the editor or moving to an unmarked target removes the inactive paint.

Style the paint

Plate publishes neutral output hooks. Product CSS owns their appearance:

[data-editor-inactive-selection] {
  background: color-mix(in oklab, var(--primary) 25%, transparent);
}
 
[data-editor-inactive-selection-caret] {
  width: 2px;
  background: var(--primary);
}
[data-editor-inactive-selection] {
  background: color-mix(in oklab, var(--primary) 25%, transparent);
}
 
[data-editor-inactive-selection-caret] {
  width: 2px;
  background: var(--primary);
}

The copied Editor registry component already styles both hooks.

Focus behavior

Focus targetInactive paint
Marked control or composed ancestorOne highlight or caret from the originating EditorContent
The editorNone; the browser owns the active selection
Any unmarked controlNone
Window blur or missing focus targetNone

The marker controls presentation only. It does not modify the document, selection, history, clipboard, or collaboration state.

Node selection rendering

Exact node selection is model state, not a native browser range. The copied Editor composes NodeSelectionHighlight and NodeSelectionDrag after EditorContent to render selected blocks and support blank-space pointer drag.

Use the Node Selection components for the DOM contract and the Node Selection demo for the copied Editor composition. Feature UI such as Block Menu reads the same exact node membership instead of maintaining another selected-node store.

Related selection features

Keep feature-specific behavior with the feature that owns it:

NeedGuide
Configure mark and inline boundary affinityEditing Behavior and Plugin Rules
Apply context-menu actions to selected blocksBlock Menu
Search text with transient match highlightsFind
Preserve selection while editing a linkLink
Publish and render remote selectionsCollaboration
Build a tag-oriented select editorMulti Select

API Reference

  • Selection API
  • Selection methods
  • DOM API
  • Node Selection components

DOM reconciliation

Each mounted EditorContent orders post-model work: model updates, DOM reads, DOM or React writes, then selection export and repair. Focus restoration, scrolling, composition repair, and selection projection share that view's lifetime. Normal application actions update model selection and let this owner reconcile the browser.

Horizontal caret movement follows the browser's visual order, including mixed direction text. Pure point transforms remain logical and work without DOM. Custom integrations can inspect a mounted caret step with editor.api.dom.resolveVisualPoint(point, { direction: 'left', unit: 'word' }). A missing DOM mapping returns null.

Hidden and external content

DOM coverage declares whether hidden content is skipped, selectable in the model, or materialized before selection enters it. Copy has a separate policy and can include model content that has no mounted DOM.

An external text view keeps canonical text in the document. Only the focused adapter owns native selection. Other views paint model selection without moving focus. A selection crossing native and external text remains complete in the model; its partial native range must not overwrite it. The adapter owns its own search, accessibility, and print integration.

Virtualized rendering omits offscreen blocks. A model selection can span the complete document while browser selection and find only see mounted content. Use a complete view when native search, accessibility traversal, printing, or DOM integrations require the whole tree.

Verify selection behavior

Use the Browser helpers to inspect the model selection, native caret or range, focus, and displayed highlights. After asserting the target state, type again and verify the text and caret. A correct model range alone does not establish that browser input and visible selection are correct.

Keyboard-selectable content

A non-void, isolating element can declare schema.element.keyboardSelectable: true when it owns both an asset and editable child content. Selecting the owner node focuses the asset and keeps native selection empty. ArrowDown enters its text; ArrowUp from the leading visual boundary returns to node selection. Backspace or Delete removes a selected owner.

Clicking a noneditable descendant selects the owner; clicking editable text selects text. Use useElementSelected({ mode: 'node' }) for the exact asset selection. The default intersection mode also matches text selection inside it.

(
null
);