Skip to main content
Selection in Lexical represents where the cursor is positioned and what content (if any) is selected. Unlike the DOM’s selection model, Lexical provides a type-safe, framework-agnostic selection system.

Selection Types

Lexical supports three types of selection:

RangeSelection

The most common type, representing a cursor or text selection:
import { $getSelection, $isRangeSelection } from 'lexical';

editor.read(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    console.log('Range selection');
  }
});
A RangeSelection has:
  • anchor: Where the selection started
  • focus: Where the selection ended
  • format: Text formatting flags
  • style: CSS style string

NodeSelection

Represents one or more selected nodes (like selected images):
import { $getSelection, $isNodeSelection } from 'lexical';

editor.read(() => {
  const selection = $getSelection();
  
  if ($isNodeSelection(selection)) {
    const nodes = selection.getNodes();
    console.log('Selected', nodes.length, 'nodes');
  }
});

GridSelection

Used for table cell selection:
import { $getSelection, $isGridSelection } from 'lexical';

editor.read(() => {
  const selection = $getSelection();
  
  if ($isGridSelection(selection)) {
    console.log('Grid selection in table');
  }
});

Getting Selection

import { $getSelection } from 'lexical';

editor.read(() => {
  const selection = $getSelection();
  
  if (selection === null) {
    console.log('No selection');
    return;
  }
  
  // Work with selection
});
$getSelection() can only be called within editor.update() or editor.read() contexts.

RangeSelection

Anchor and Focus

A RangeSelection consists of two points:
editor.read(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    const { anchor, focus } = selection;
    
    console.log('Anchor:', anchor.key, anchor.offset, anchor.type);
    console.log('Focus:', focus.key, focus.offset, focus.type);
  }
});
Each point has:
  • key: The node key
  • offset: Position within the node
  • type: Either 'text' or 'element'

Point Types

Text Point

Points to a position within a TextNode:
// anchor.type === 'text'
// anchor.offset is character position
const textNode = anchor.getNode(); // TextNode

Element Point

Points to a child position within an ElementNode:
// anchor.type === 'element'
// anchor.offset is child index
const elementNode = anchor.getNode(); // ElementNode

Collapsed Selection

When anchor and focus are identical:
editor.read(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    if (selection.isCollapsed()) {
      console.log('Cursor position (no text selected)');
    } else {
      console.log('Text is selected');
    }
  }
});

Backward Selection

When focus comes before anchor in the document:
editor.read(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    if (selection.isBackward()) {
      console.log('Selection made from right to left');
    }
  }
});

Getting Selected Nodes

editor.read(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    const nodes = selection.getNodes();
    
    nodes.forEach(node => {
      console.log('Selected node:', node.getType());
    });
  }
});

Getting Text Content

editor.read(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    const text = selection.getTextContent();
    console.log('Selected text:', text);
  }
});

Manipulating Selection

Setting Selection

import { $setSelection, $createRangeSelection } from 'lexical';

editor.update(() => {
  const newSelection = $createRangeSelection();
  $setSelection(newSelection);
});

Selecting Nodes

editor.update(() => {
  const node = $getRoot().getFirstChild();
  
  if (node) {
    // Select entire node
    node.select();
    
    // Select start of node
    node.selectStart();
    
    // Select end of node
    node.selectEnd();
  }
});

Selecting Text

For TextNodes:
editor.update(() => {
  const textNode = $createTextNode('Hello, world!');
  
  // Select specific range
  textNode.select(0, 5); // Selects "Hello"
  
  // Select all text
  textNode.select();
});

Moving Selection

import { $getSelection, $isRangeSelection } from 'lexical';

editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    // Move selection
    selection.modify(
      'move',      // 'move' or 'extend'
      false,       // backward?
      'character'  // 'character' | 'word' | 'lineboundary'
    );
  }
});

Inserting Content

Insert Text

editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    selection.insertText('Inserted text');
  }
});

Insert Raw Text

Preserves tabs and newlines as nodes:
editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    // Tabs become TabNodes, \n becomes LineBreakNodes
    selection.insertRawText('Line 1\nLine 2\tTabbed');
  }
});

Insert Nodes

editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    selection.insertNodes([
      $createTextNode('First'),
      $createLineBreakNode(),
      $createTextNode('Second'),
    ]);
  }
});

Insert Paragraph

editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    selection.insertParagraph();
  }
});

Deleting Content

Remove Text

editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    selection.removeText();
  }
});

Extract Nodes

Remove and return selected nodes:
editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    const extractedNodes = selection.extract();
    // Nodes are removed from the tree
  }
});

Formatting

Text Format

import { FORMAT_TEXT_COMMAND } from 'lexical';

editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    // Toggle format
    selection.toggleFormat('bold');
    
    // Set format flags directly
    selection.setFormat(IS_BOLD | IS_ITALIC);
    
    // Check format
    if (selection.hasFormat('bold')) {
      console.log('Selection is bold');
    }
  }
});

Apply Formatting to Selection

editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    selection.formatText('italic');
  }
});

Style

editor.update(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    // Set CSS style
    selection.setStyle('color: red; font-size: 18px');
    
    // Get current style
    const style = selection.style;
  }
});

NodeSelection

For selecting entire nodes (images, embeds, etc.):
import { $createNodeSelection, $setSelection } from 'lexical';

editor.update(() => {
  const nodeSelection = $createNodeSelection();
  
  // Add nodes to selection
  nodeSelection.add(imageNode.getKey());
  nodeSelection.add(videoNode.getKey());
  
  $setSelection(nodeSelection);
});

Manipulating NodeSelection

editor.update(() => {
  const selection = $getSelection();
  
  if ($isNodeSelection(selection)) {
    // Add node
    selection.add(nodeKey);
    
    // Remove node
    selection.delete(nodeKey);
    
    // Check if node is selected
    if (selection.has(nodeKey)) {
      console.log('Node is selected');
    }
    
    // Clear selection
    selection.clear();
    
    // Get selected nodes
    const nodes = selection.getNodes();
  }
});

Selection Listeners

Listen to selection changes:
import { SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_NORMAL } from 'lexical';

editor.registerCommand(
  SELECTION_CHANGE_COMMAND,
  () => {
    const selection = $getSelection();
    
    if ($isRangeSelection(selection)) {
      // Handle selection change
    }
    
    return false;
  },
  COMMAND_PRIORITY_NORMAL
);
Or use update listeners:
editor.registerUpdateListener(({ editorState }) => {
  editorState.read(() => {
    const selection = $getSelection();
    // React to selection changes
  });
});

Advanced Selection

Clone Selection

editor.read(() => {
  const selection = $getSelection();
  
  if ($isRangeSelection(selection)) {
    const cloned = selection.clone();
  }
});

Compare Selections

editor.read(() => {
  const selection1 = $getSelection();
  const selection2 = /* ... */;
  
  if (selection1 && selection2 && selection1.is(selection2)) {
    console.log('Selections are equal');
  }
});

Apply DOM Range

Map a DOM selection range to Lexical:
editor.update(() => {
  const selection = $getSelection();
  const domSelection = window.getSelection();
  
  if ($isRangeSelection(selection) && domSelection) {
    const range = domSelection.getRangeAt(0);
    selection.applyDOMRange(range);
  }
});

Best Practices

Use type guards before accessing type-specific properties:
const selection = $getSelection();

if ($isRangeSelection(selection)) {
  // Safe to use RangeSelection methods
  selection.insertText('...');
}
Selection can be null when the editor is not focused:
const selection = $getSelection();

if (!selection) {
  console.log('No active selection');
  return;
}
For large selections, getNodes() can return many nodes. Consider using more targeted queries when possible.
Some operations clear selection. Clone it first if you need to restore:
const selectionClone = selection.clone();
// ... operations that might clear selection
$setSelection(selectionClone);

Type Signatures

interface BaseSelection {
  clone(): BaseSelection;
  extract(): Array<LexicalNode>;
  getNodes(): Array<LexicalNode>;
  getTextContent(): string;
  insertText(text: string): void;
  insertRawText(text: string): void;
  is(selection: null | BaseSelection): boolean;
  insertNodes(nodes: Array<LexicalNode>): void;
}

class RangeSelection implements BaseSelection {
  anchor: PointType;
  focus: PointType;
  format: number;
  style: string;
  
  isCollapsed(): boolean;
  isBackward(): boolean;
  toggleFormat(format: TextFormatType): void;
  setFormat(format: number): void;
  hasFormat(type: TextFormatType): boolean;
  modify(
    alter: 'move' | 'extend',
    isBackward: boolean,
    granularity: 'character' | 'word' | 'lineboundary'
  ): void;
  removeText(): void;
  formatText(formatType: TextFormatType): void;
  insertParagraph(): ElementNode | null;
  insertLineBreak(selectStart?: boolean): void;
}

class NodeSelection implements BaseSelection {
  add(key: NodeKey): void;
  delete(key: NodeKey): void;
  clear(): void;
  has(key: NodeKey): boolean;
}
  • Updates - Modifying content at selection
  • Nodes - The nodes that selection points to
  • Commands - Selection-related commands
  • Editor State - Selection is part of editor state

Build docs developers (and LLMs) love