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.
| Claim | Assert | Owner |
|---|---|---|
| The Slate selection is correct | model selection | @platejs/slate |
| The browser caret lands in the right DOM text | DOM caret or DOM selection | @platejs/slate-react and @platejs/slate-dom |
| The visible browser highlight is not duplicated | displayed selection snapshot | @platejs/browser |
| Hidden content remains selectable or copyable | DOM coverage policy plus model result | @platejs/slate-dom and @platejs/slate-react |
| Follow-up typing still works | text, 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:
| Selection | What it means |
|---|---|
| Model selection | The Range stored by the Slate editor. Transactions read and write this value. |
| Native selection | The 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.
| Policy | Use 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
- Range API
- Point API
- Transforms API
- Clipboard And Paste
- Projection And Overlays
- React Editor
- DOM Coverage Boundaries
- Slate Browser
Done. You can now say which selection layer owns the bug before writing the fix or the proof.