0
# Node System
1
2
Comprehensive node types and utilities for representing and manipulating document content. Lexical uses a tree-based node structure where each node represents a piece of content with specific behavior and rendering characteristics.
3
4
## Capabilities
5
6
### Base Node Class
7
8
Abstract base class that all Lexical nodes extend. Provides core functionality for serialization, DOM interaction, and tree manipulation.
9
10
```typescript { .api }
11
/**
12
* Abstract base class for all Lexical nodes
13
*/
14
abstract class LexicalNode {
15
/** Get the unique key for this node */
16
getKey(): NodeKey;
17
/** Get the type identifier for this node */
18
getType(): string;
19
/** Create a clone of this node */
20
clone(): LexicalNode;
21
/** Create DOM representation of this node */
22
createDOM(config: EditorConfig): HTMLElement;
23
/** Update existing DOM element for this node */
24
updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean;
25
/** Export node to JSON format */
26
exportJSON(): SerializedLexicalNode;
27
/** Export node to DOM format */
28
exportDOM(editor: LexicalEditor): DOMExportOutput;
29
/** Get the parent node */
30
getParent(): ElementNode | null;
31
/** Get the parent node or throw if none */
32
getParentOrThrow(): ElementNode;
33
/** Get all ancestors of this node */
34
getParents(): Array<ElementNode>;
35
/** Get the root node */
36
getTopLevelElement(): ElementNode | null;
37
/** Check if this node is attached to the root */
38
isAttached(): boolean;
39
/** Check if this node is selected */
40
isSelected(): boolean;
41
/** Remove this node from its parent */
42
remove(preserveEmptyParent?: boolean): void;
43
/** Replace this node with another node */
44
replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N;
45
/** Insert a node after this node */
46
insertAfter(nodeToInsert: LexicalNode, restoreSelection?: boolean): LexicalNode;
47
/** Insert a node before this node */
48
insertBefore(nodeToInsert: LexicalNode, restoreSelection?: boolean): LexicalNode;
49
/** Check if this node can be replaced by another node */
50
canReplaceWith(replacement: LexicalNode): boolean;
51
/** Select this node */
52
selectStart(): RangeSelection;
53
/** Select to the end of this node */
54
selectEnd(): RangeSelection;
55
/** Select the entire node */
56
selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection;
57
/** Select the previous node */
58
selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection;
59
}
60
61
interface SerializedLexicalNode {
62
type: string;
63
version: number;
64
}
65
66
interface DOMExportOutput {
67
element: HTMLElement;
68
after?: (generatedElement: HTMLElement) => void;
69
}
70
71
type NodeKey = string;
72
```
73
74
### Text Node
75
76
Node type for text content with formatting capabilities.
77
78
```typescript { .api }
79
/**
80
* Node for text content with formatting
81
*/
82
class TextNode extends LexicalNode {
83
/** Create a new TextNode */
84
constructor(text: string, key?: NodeKey);
85
/** Get the text content */
86
getTextContent(): string;
87
/** Set the text content */
88
setTextContent(text: string): this;
89
/** Check if text has specific formatting */
90
hasFormat(type: TextFormatType): boolean;
91
/** Toggle specific text formatting */
92
toggleFormat(type: TextFormatType): this;
93
/** Get all applied formats */
94
getFormat(): number;
95
/** Set text formatting using bitmask */
96
setFormat(format: number): this;
97
/** Get text mode (normal, token, segmented) */
98
getMode(): TextModeType;
99
/** Set text mode */
100
setMode(mode: TextModeType): this;
101
/** Check if node can be merged with adjacent text */
102
canHaveFormat(): boolean;
103
/** Split text at offset */
104
splitText(...splitOffsets: Array<number>): Array<TextNode>;
105
/** Merge with adjacent text node */
106
mergeWithSibling(target: TextNode): TextNode;
107
}
108
109
/**
110
* Create a new TextNode
111
* @param text - Initial text content
112
* @returns New TextNode instance
113
*/
114
function $createTextNode(text?: string): TextNode;
115
116
/**
117
* Check if node is a TextNode
118
* @param node - Node to check
119
* @returns True if node is TextNode
120
*/
121
function $isTextNode(node: LexicalNode | null | undefined): node is TextNode;
122
123
interface SerializedTextNode extends SerializedLexicalNode {
124
detail: number;
125
format: number;
126
mode: TextModeType;
127
style: string;
128
text: string;
129
}
130
131
type TextFormatType = 'bold' | 'italic' | 'strikethrough' | 'underline' | 'code' | 'subscript' | 'superscript' | 'highlight';
132
type TextModeType = 'normal' | 'token' | 'segmented';
133
```
134
135
**Usage Examples:**
136
137
```typescript
138
import { $createTextNode, $isTextNode } from "lexical";
139
140
// Create text node
141
const textNode = $createTextNode('Hello, world!');
142
143
// Format text
144
textNode.toggleFormat('bold');
145
textNode.toggleFormat('italic');
146
147
// Check formatting
148
if (textNode.hasFormat('bold')) {
149
console.log('Text is bold');
150
}
151
152
// Split text
153
const [first, second] = textNode.splitText(5); // Split at "Hello"
154
155
// Type checking
156
if ($isTextNode(someNode)) {
157
console.log(someNode.getTextContent());
158
}
159
```
160
161
### Element Node
162
163
Base class for nodes that can contain child nodes.
164
165
```typescript { .api }
166
/**
167
* Base class for nodes with children
168
*/
169
class ElementNode extends LexicalNode {
170
/** Get all child nodes */
171
getChildren<T extends LexicalNode>(): Array<T>;
172
/** Get child nodes filtered by type */
173
getChildrenByType<T extends LexicalNode>(type: string): Array<T>;
174
/** Get number of child nodes */
175
getChildrenSize(): number;
176
/** Check if node is empty (no children) */
177
isEmpty(): boolean;
178
/** Check if node is dirty (needs DOM update) */
179
isDirty(): boolean;
180
/** Get all descendant nodes */
181
getAllTextNodes(): Array<TextNode>;
182
/** Get first child node */
183
getFirstChild<T extends LexicalNode>(): T | null;
184
/** Get first child or throw if none */
185
getFirstChildOrThrow<T extends LexicalNode>(): T;
186
/** Get last child node */
187
getLastChild<T extends LexicalNode>(): T | null;
188
/** Get last child or throw if none */
189
getLastChildOrThrow<T extends LexicalNode>(): T;
190
/** Get child at specific index */
191
getChildAtIndex<T extends LexicalNode>(index: number): T | null;
192
/** Get text content of all children */
193
getTextContent(): string;
194
/** Get direction (ltr or rtl) */
195
getDirection(): 'ltr' | 'rtl' | null;
196
/** Get element format (alignment) */
197
getFormatType(): ElementFormatType;
198
/** Set element format */
199
setFormat(type: ElementFormatType): this;
200
/** Get indentation level */
201
getIndent(): number;
202
/** Set indentation level */
203
setIndent(indentLevel: number): this;
204
/** Append child nodes */
205
append(...nodesToAppend: LexicalNode[]): this;
206
/** Clear all children */
207
clear(): this;
208
/** Insert child at index */
209
splice(start: number, deleteCount: number, ...nodesToInsert: LexicalNode[]): this;
210
/** Select this element */
211
select(anchorOffset?: number, focusOffset?: number): RangeSelection;
212
/** Select all content in this element */
213
selectStart(): RangeSelection;
214
/** Select to end of this element */
215
selectEnd(): RangeSelection;
216
/** Can extract with child nodes during operations */
217
extractWithChild(child: LexicalNode, selection: BaseSelection, destination: 'clone' | 'html'): boolean;
218
/** Can be merged with adjacent elements */
219
canMergeWith(node: ElementNode): boolean;
220
/** Can insert text before this element */
221
canInsertTextBefore(): boolean;
222
/** Can insert text after this element */
223
canInsertTextAfter(): boolean;
224
/** Can be indented */
225
canIndent(): boolean;
226
}
227
228
/**
229
* Check if node is an ElementNode
230
* @param node - Node to check
231
* @returns True if node is ElementNode
232
*/
233
function $isElementNode(node: LexicalNode | null | undefined): node is ElementNode;
234
235
interface SerializedElementNode extends SerializedLexicalNode {
236
children: Array<SerializedLexicalNode>;
237
direction: 'ltr' | 'rtl' | null;
238
format: ElementFormatType | '';
239
indent: number;
240
}
241
242
type ElementFormatType = 'left' | 'center' | 'right' | 'justify' | 'start' | 'end';
243
```
244
245
### Paragraph Node
246
247
Standard paragraph node for block-level text content.
248
249
```typescript { .api }
250
/**
251
* Node for paragraph elements
252
*/
253
class ParagraphNode extends ElementNode {
254
/** Create a new ParagraphNode */
255
constructor(key?: NodeKey);
256
/** Get text format for paragraph-level formatting */
257
getTextFormat(): number;
258
/** Set text format for paragraph */
259
setTextFormat(format: number): this;
260
/** Check if paragraph has text format */
261
hasTextFormat(type: TextFormatType): boolean;
262
}
263
264
/**
265
* Create a new ParagraphNode
266
* @returns New ParagraphNode instance
267
*/
268
function $createParagraphNode(): ParagraphNode;
269
270
/**
271
* Check if node is a ParagraphNode
272
* @param node - Node to check
273
* @returns True if node is ParagraphNode
274
*/
275
function $isParagraphNode(node: LexicalNode | null | undefined): node is ParagraphNode;
276
277
interface SerializedParagraphNode extends SerializedElementNode {
278
textFormat: number;
279
textStyle: string;
280
}
281
```
282
283
### Root Node
284
285
Special node serving as the document root.
286
287
```typescript { .api }
288
/**
289
* Root node of the editor (singleton)
290
*/
291
class RootNode extends ElementNode {
292
/** Create a new RootNode (typically only used internally) */
293
constructor();
294
/** Root nodes cannot be removed */
295
remove(): never;
296
/** Root nodes cannot be replaced */
297
replace<N extends LexicalNode>(node: N): never;
298
/** Root nodes cannot be inserted after */
299
insertAfter(node: LexicalNode): never;
300
/** Root nodes cannot be inserted before */
301
insertBefore(node: LexicalNode): never;
302
}
303
304
/**
305
* Check if node is the RootNode
306
* @param node - Node to check
307
* @returns True if node is RootNode
308
*/
309
function $isRootNode(node: LexicalNode | null | undefined): node is RootNode;
310
311
interface SerializedRootNode extends SerializedElementNode {
312
// Root node has no additional properties
313
}
314
```
315
316
### Line Break Node
317
318
Node for line breaks (br elements).
319
320
```typescript { .api }
321
/**
322
* Node for line breaks
323
*/
324
class LineBreakNode extends LexicalNode {
325
/** Create a new LineBreakNode */
326
constructor(key?: NodeKey);
327
/** Line breaks have no text content */
328
getTextContent(): '';
329
}
330
331
/**
332
* Create a new LineBreakNode
333
* @returns New LineBreakNode instance
334
*/
335
function $createLineBreakNode(): LineBreakNode;
336
337
/**
338
* Check if node is a LineBreakNode
339
* @param node - Node to check
340
* @returns True if node is LineBreakNode
341
*/
342
function $isLineBreakNode(node: LexicalNode | null | undefined): node is LineBreakNode;
343
344
interface SerializedLineBreakNode extends SerializedLexicalNode {
345
// LineBreak node has no additional properties
346
}
347
```
348
349
### Tab Node
350
351
Specialized TextNode for tab characters.
352
353
```typescript { .api }
354
/**
355
* Specialized TextNode for tab characters
356
*/
357
class TabNode extends TextNode {
358
/** Create a new TabNode */
359
constructor(key?: NodeKey);
360
/** Tabs cannot have format */
361
canHaveFormat(): false;
362
}
363
364
/**
365
* Create a new TabNode
366
* @returns New TabNode instance
367
*/
368
function $createTabNode(): TabNode;
369
370
/**
371
* Check if node is a TabNode
372
* @param node - Node to check
373
* @returns True if node is TabNode
374
*/
375
function $isTabNode(node: LexicalNode | null | undefined): node is TabNode;
376
377
interface SerializedTabNode extends SerializedTextNode {
378
// Tab inherits from text but has constrained behavior
379
}
380
```
381
382
### Decorator Node
383
384
Base class for custom decorator nodes that render non-text content.
385
386
```typescript { .api }
387
/**
388
* Base class for custom decorator nodes
389
*/
390
abstract class DecoratorNode<T = unknown> extends LexicalNode {
391
/** Create DOM representation for decorator */
392
abstract decorate(editor: LexicalEditor, config: EditorConfig): T;
393
/** Whether decorator is inline or block */
394
isIsolated(): boolean;
395
/** Whether decorator can be selected */
396
isKeyboardSelectable(): boolean;
397
/** Whether selection can be placed before this node */
398
canInsertTextBefore(): boolean;
399
/** Whether selection can be placed after this node */
400
canInsertTextAfter(): boolean;
401
}
402
403
/**
404
* Check if node is a DecoratorNode
405
* @param node - Node to check
406
* @returns True if node is DecoratorNode
407
*/
408
function $isDecoratorNode(node: LexicalNode | null | undefined): node is DecoratorNode;
409
```
410
411
### Node Utilities
412
413
Utility functions for working with nodes.
414
415
```typescript { .api }
416
/**
417
* Build import map for node serialization
418
* @param nodes - Array of node classes or replacements
419
* @returns Map of node types to classes
420
*/
421
function buildImportMap(
422
nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>
423
): Map<string, Klass<LexicalNode>>;
424
425
// Node-related type definitions
426
type Klass<T extends LexicalNode> = {
427
new (...args: any[]): T;
428
} & Omit<LexicalNode, keyof T>;
429
430
interface LexicalNodeReplacement {
431
replace: Klass<LexicalNode>;
432
with: <T extends LexicalNode>(node: LexicalNode) => T;
433
withKlass: Klass<LexicalNode>;
434
}
435
436
type NodeMap = Map<NodeKey, LexicalNode>;
437
```
438
439
**Usage Examples:**
440
441
```typescript
442
import {
443
$createParagraphNode,
444
$createTextNode,
445
$isParagraphNode,
446
$getRoot
447
} from "lexical";
448
449
// Create and structure nodes
450
const paragraph = $createParagraphNode();
451
const text = $createTextNode('Hello, world!');
452
const breakNode = $createLineBreakNode();
453
const moreText = $createTextNode('Second line');
454
455
// Build document structure
456
paragraph.append(text, breakNode, moreText);
457
458
// Get root and append
459
const root = $getRoot();
460
root.append(paragraph);
461
462
// Work with children
463
const children = paragraph.getChildren();
464
console.log('Number of children:', paragraph.getChildrenSize());
465
466
// Type checking and manipulation
467
if ($isParagraphNode(someNode)) {
468
someNode.setFormat('center');
469
someNode.setIndent(1);
470
}
471
```