Selection And DOM

PreviousNext

Understand model selection, browser-native selection, DOM coverage, voids, hidden content, and browser proof.

Slate stores selection in the model, then reconciles it with browser-native selection in React. Use this page for caret and DOM rules; use Editing Behavior for the full event-to-commit pipeline.

Choose The Right Assertion

Selection bugs need the layer that can actually fail.

ClaimAssertOwner
The Slate selection is correctmodel selection@platejs/slate
The browser caret lands in the right DOM textDOM caret or DOM selection@platejs/slate-react and @platejs/slate-dom
The visible browser highlight is not duplicateddisplayed selection snapshot@platejs/browser
Hidden content remains selectable or copyableDOM coverage policy plus model result@platejs/slate-dom and @platejs/slate-react
Follow-up typing still workstext, selection, focus, and commit after the asserted state@platejs/browser

Do not close a selection bug with only one layer unless the bug only exists in that layer.

Selection Ownership

Slate has two selection systems in a React editor:

SelectionWhat it means
Model selectionThe Range stored by the Slate editor. Transactions read and write this value.
Native selectionThe browser Selection visible in the contenteditable DOM.

Most editor code should write the model selection and let Slate export the DOM/native selection. Slate imports the native selection when the browser is the current source of truth, such as user selection movement or native input.

Model Selection

Read selection through editor.read(...).

const selection = editor.read((state) => state.selection.get());
const selection = editor.read((state) => state.selection.get());

Write selection inside editor.update(...).

editor.update((tx) => {
  tx.selection.set({
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [1, 0], offset: 3 },
  });
});
editor.update((tx) => {
  tx.selection.set({
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [1, 0], offset: 3 },
  });
});

Use Locations for Path, Point, Range, and refs. Use Transforms for transaction selection helpers.

Native Selection

The browser can move native selection through mouse, keyboard, touch, composition, spellcheck, selection handles, and platform editing commands. Slate React observes those moves and imports them when they should become model state.

Application code should not patch the browser Selection directly for normal editor behavior. Use model transactions, Editable handlers, or DOM-aware React APIs that preserve Slate's repair path.

Use React Editor for DOM translation helpers and Slate React Event Handling for browser event hooks.

DOM Coverage

DOM coverage boundaries represent model content whose editable DOM is not mounted.

PolicyUse it when
selectionPolicy="skip"Hidden content is app chrome or should not receive cursor movement.
selectionPolicy="model"Hidden content can be selected in the Slate model without mounting DOM.
selectionPolicy="materialize"Slate should ask the app to reveal content before moving selection into it.

Clipboard has its own policy. Model-backed copy can include hidden document content without selecting every hidden DOM node. Use Clipboard And Paste for copy, paste, drop, and fragment import ownership.

Use DOM Coverage Boundaries for selectionPolicy, copyPolicy, findPolicy, and materialization behavior.

Voids And Editable Islands

Void elements still need a model text anchor so Slate can place selection around them. Editable islands keep an element void for outer rendering policy while allowing cursor projection into its text children.

import { defineEditorExtension } from "@platejs/slate";
 
const media = defineEditorExtension({
  name: "media",
  elements: [
    {
      type: "captioned-image",
      void: "editable-island",
    },
  ],
});
import { defineEditorExtension } from "@platejs/slate";
 
const media = defineEditorExtension({
  name: "media",
  elements: [
    {
      type: "captioned-image",
      void: "editable-island",
    },
  ],
});

Keep visible void UI inside the void renderer. Let Slate render the shell and text anchor. Use Editable Component for void rendering rules.

Large Documents

Large documents may not have a complete DOM mounted at every moment. Editable can use DOM strategies such as auto, staged, full, or virtualized to keep the active editing surface responsive.

That changes what native DOM can prove. A model selection can cover the full document while the mounted DOM only covers the active window or materialized boundary.

Use Editable Component for domStrategy and Virtualized Rendering for the experimental lane.

Browser Proof

Selection proof should assert the layers named by the claim.

import { openExample } from "@platejs/browser/playwright";
 
const editor = await openExample(page, "plaintext", {
  ready: { editor: "visible" },
});
 
await editor.focus();
await editor.selectAll();
await editor.type("Hello");
 
await editor.assert.selection({
  anchor: { path: [0, 0], offset: 5 },
  focus: { path: [0, 0], offset: 5 },
});
 
await editor.assert.domCaret({ text: "Hello", offset: 5 });
await editor.assert.noDoubleSelectionHighlight();
await editor.type("!");
await editor.assert.text("Hello!");
import { openExample } from "@platejs/browser/playwright";
 
const editor = await openExample(page, "plaintext", {
  ready: { editor: "visible" },
});
 
await editor.focus();
await editor.selectAll();
await editor.type("Hello");
 
await editor.assert.selection({
  anchor: { path: [0, 0], offset: 5 },
  focus: { path: [0, 0], offset: 5 },
});
 
await editor.assert.domCaret({ text: "Hello", offset: 5 });
await editor.assert.noDoubleSelectionHighlight();
await editor.type("!");
await editor.assert.text("Hello!");

Use Slate Browser for Playwright helpers that capture model selection, DOM selection, displayed selection, focus, native event traces, clipboard, screenshots, and follow-up typing.

Reference

Done. You can now say which selection layer owns the bug before writing the fix or the proof.