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 \n Line 2 \t Tabbed' );
}
});
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 ();
}
});
Remove and return selected nodes:
editor . update (() => {
const selection = $getSelection ();
if ( $isRangeSelection ( selection )) {
const extractedNodes = selection . extract ();
// Nodes are removed from the tree
}
});
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' );
}
}
});
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
Always check selection type
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 ;
}
Use selection.getNodes() carefully
For large selections, getNodes() can return many nodes. Consider using more targeted queries when possible.
Preserve selection when needed
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