Lexical is an extensible text editor framework that provides excellent reliability, accessible and performance.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
Abstract base class that all Lexical nodes extend. Provides core functionality for serialization, DOM interaction, and tree manipulation.
/**
* Abstract base class for all Lexical nodes
*/
abstract class LexicalNode {
/** Get the unique key for this node */
getKey(): NodeKey;
/** Get the type identifier for this node */
getType(): string;
/** Create a clone of this node */
clone(): LexicalNode;
/** Create DOM representation of this node */
createDOM(config: EditorConfig): HTMLElement;
/** Update existing DOM element for this node */
updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean;
/** Export node to JSON format */
exportJSON(): SerializedLexicalNode;
/** Export node to DOM format */
exportDOM(editor: LexicalEditor): DOMExportOutput;
/** Get the parent node */
getParent(): ElementNode | null;
/** Get the parent node or throw if none */
getParentOrThrow(): ElementNode;
/** Get all ancestors of this node */
getParents(): Array<ElementNode>;
/** Get the root node */
getTopLevelElement(): ElementNode | null;
/** Check if this node is attached to the root */
isAttached(): boolean;
/** Check if this node is selected */
isSelected(): boolean;
/** Remove this node from its parent */
remove(preserveEmptyParent?: boolean): void;
/** Replace this node with another node */
replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N;
/** Insert a node after this node */
insertAfter(nodeToInsert: LexicalNode, restoreSelection?: boolean): LexicalNode;
/** Insert a node before this node */
insertBefore(nodeToInsert: LexicalNode, restoreSelection?: boolean): LexicalNode;
/** Check if this node can be replaced by another node */
canReplaceWith(replacement: LexicalNode): boolean;
/** Select this node */
selectStart(): RangeSelection;
/** Select to the end of this node */
selectEnd(): RangeSelection;
/** Select the entire node */
selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection;
/** Select the previous node */
selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection;
}
interface SerializedLexicalNode {
type: string;
version: number;
}
interface DOMExportOutput {
element: HTMLElement;
after?: (generatedElement: HTMLElement) => void;
}
type NodeKey = string;Node type for text content with formatting capabilities.
/**
* Node for text content with formatting
*/
class TextNode extends LexicalNode {
/** Create a new TextNode */
constructor(text: string, key?: NodeKey);
/** Get the text content */
getTextContent(): string;
/** Set the text content */
setTextContent(text: string): this;
/** Check if text has specific formatting */
hasFormat(type: TextFormatType): boolean;
/** Toggle specific text formatting */
toggleFormat(type: TextFormatType): this;
/** Get all applied formats */
getFormat(): number;
/** Set text formatting using bitmask */
setFormat(format: number): this;
/** Get text mode (normal, token, segmented) */
getMode(): TextModeType;
/** Set text mode */
setMode(mode: TextModeType): this;
/** Check if node can be merged with adjacent text */
canHaveFormat(): boolean;
/** Split text at offset */
splitText(...splitOffsets: Array<number>): Array<TextNode>;
/** Merge with adjacent text node */
mergeWithSibling(target: TextNode): TextNode;
}
/**
* Create a new TextNode
* @param text - Initial text content
* @returns New TextNode instance
*/
function $createTextNode(text?: string): TextNode;
/**
* Check if node is a TextNode
* @param node - Node to check
* @returns True if node is TextNode
*/
function $isTextNode(node: LexicalNode | null | undefined): node is TextNode;
interface SerializedTextNode extends SerializedLexicalNode {
detail: number;
format: number;
mode: TextModeType;
style: string;
text: string;
}
type TextFormatType = 'bold' | 'italic' | 'strikethrough' | 'underline' | 'code' | 'subscript' | 'superscript' | 'highlight';
type TextModeType = 'normal' | 'token' | 'segmented';Usage Examples:
import { $createTextNode, $isTextNode } from "lexical";
// Create text node
const textNode = $createTextNode('Hello, world!');
// Format text
textNode.toggleFormat('bold');
textNode.toggleFormat('italic');
// Check formatting
if (textNode.hasFormat('bold')) {
console.log('Text is bold');
}
// Split text
const [first, second] = textNode.splitText(5); // Split at "Hello"
// Type checking
if ($isTextNode(someNode)) {
console.log(someNode.getTextContent());
}Base class for nodes that can contain child nodes.
/**
* Base class for nodes with children
*/
class ElementNode extends LexicalNode {
/** Get all child nodes */
getChildren<T extends LexicalNode>(): Array<T>;
/** Get child nodes filtered by type */
getChildrenByType<T extends LexicalNode>(type: string): Array<T>;
/** Get number of child nodes */
getChildrenSize(): number;
/** Check if node is empty (no children) */
isEmpty(): boolean;
/** Check if node is dirty (needs DOM update) */
isDirty(): boolean;
/** Get all descendant nodes */
getAllTextNodes(): Array<TextNode>;
/** Get first child node */
getFirstChild<T extends LexicalNode>(): T | null;
/** Get first child or throw if none */
getFirstChildOrThrow<T extends LexicalNode>(): T;
/** Get last child node */
getLastChild<T extends LexicalNode>(): T | null;
/** Get last child or throw if none */
getLastChildOrThrow<T extends LexicalNode>(): T;
/** Get child at specific index */
getChildAtIndex<T extends LexicalNode>(index: number): T | null;
/** Get text content of all children */
getTextContent(): string;
/** Get direction (ltr or rtl) */
getDirection(): 'ltr' | 'rtl' | null;
/** Get element format (alignment) */
getFormatType(): ElementFormatType;
/** Set element format */
setFormat(type: ElementFormatType): this;
/** Get indentation level */
getIndent(): number;
/** Set indentation level */
setIndent(indentLevel: number): this;
/** Append child nodes */
append(...nodesToAppend: LexicalNode[]): this;
/** Clear all children */
clear(): this;
/** Insert child at index */
splice(start: number, deleteCount: number, ...nodesToInsert: LexicalNode[]): this;
/** Select this element */
select(anchorOffset?: number, focusOffset?: number): RangeSelection;
/** Select all content in this element */
selectStart(): RangeSelection;
/** Select to end of this element */
selectEnd(): RangeSelection;
/** Can extract with child nodes during operations */
extractWithChild(child: LexicalNode, selection: BaseSelection, destination: 'clone' | 'html'): boolean;
/** Can be merged with adjacent elements */
canMergeWith(node: ElementNode): boolean;
/** Can insert text before this element */
canInsertTextBefore(): boolean;
/** Can insert text after this element */
canInsertTextAfter(): boolean;
/** Can be indented */
canIndent(): boolean;
}
/**
* Check if node is an ElementNode
* @param node - Node to check
* @returns True if node is ElementNode
*/
function $isElementNode(node: LexicalNode | null | undefined): node is ElementNode;
interface SerializedElementNode extends SerializedLexicalNode {
children: Array<SerializedLexicalNode>;
direction: 'ltr' | 'rtl' | null;
format: ElementFormatType | '';
indent: number;
}
type ElementFormatType = 'left' | 'center' | 'right' | 'justify' | 'start' | 'end';Standard paragraph node for block-level text content.
/**
* Node for paragraph elements
*/
class ParagraphNode extends ElementNode {
/** Create a new ParagraphNode */
constructor(key?: NodeKey);
/** Get text format for paragraph-level formatting */
getTextFormat(): number;
/** Set text format for paragraph */
setTextFormat(format: number): this;
/** Check if paragraph has text format */
hasTextFormat(type: TextFormatType): boolean;
}
/**
* Create a new ParagraphNode
* @returns New ParagraphNode instance
*/
function $createParagraphNode(): ParagraphNode;
/**
* Check if node is a ParagraphNode
* @param node - Node to check
* @returns True if node is ParagraphNode
*/
function $isParagraphNode(node: LexicalNode | null | undefined): node is ParagraphNode;
interface SerializedParagraphNode extends SerializedElementNode {
textFormat: number;
textStyle: string;
}Special node serving as the document root.
/**
* Root node of the editor (singleton)
*/
class RootNode extends ElementNode {
/** Create a new RootNode (typically only used internally) */
constructor();
/** Root nodes cannot be removed */
remove(): never;
/** Root nodes cannot be replaced */
replace<N extends LexicalNode>(node: N): never;
/** Root nodes cannot be inserted after */
insertAfter(node: LexicalNode): never;
/** Root nodes cannot be inserted before */
insertBefore(node: LexicalNode): never;
}
/**
* Check if node is the RootNode
* @param node - Node to check
* @returns True if node is RootNode
*/
function $isRootNode(node: LexicalNode | null | undefined): node is RootNode;
interface SerializedRootNode extends SerializedElementNode {
// Root node has no additional properties
}Node for line breaks (br elements).
/**
* Node for line breaks
*/
class LineBreakNode extends LexicalNode {
/** Create a new LineBreakNode */
constructor(key?: NodeKey);
/** Line breaks have no text content */
getTextContent(): '';
}
/**
* Create a new LineBreakNode
* @returns New LineBreakNode instance
*/
function $createLineBreakNode(): LineBreakNode;
/**
* Check if node is a LineBreakNode
* @param node - Node to check
* @returns True if node is LineBreakNode
*/
function $isLineBreakNode(node: LexicalNode | null | undefined): node is LineBreakNode;
interface SerializedLineBreakNode extends SerializedLexicalNode {
// LineBreak node has no additional properties
}Specialized TextNode for tab characters.
/**
* Specialized TextNode for tab characters
*/
class TabNode extends TextNode {
/** Create a new TabNode */
constructor(key?: NodeKey);
/** Tabs cannot have format */
canHaveFormat(): false;
}
/**
* Create a new TabNode
* @returns New TabNode instance
*/
function $createTabNode(): TabNode;
/**
* Check if node is a TabNode
* @param node - Node to check
* @returns True if node is TabNode
*/
function $isTabNode(node: LexicalNode | null | undefined): node is TabNode;
interface SerializedTabNode extends SerializedTextNode {
// Tab inherits from text but has constrained behavior
}Base class for custom decorator nodes that render non-text content.
/**
* Base class for custom decorator nodes
*/
abstract class DecoratorNode<T = unknown> extends LexicalNode {
/** Create DOM representation for decorator */
abstract decorate(editor: LexicalEditor, config: EditorConfig): T;
/** Whether decorator is inline or block */
isIsolated(): boolean;
/** Whether decorator can be selected */
isKeyboardSelectable(): boolean;
/** Whether selection can be placed before this node */
canInsertTextBefore(): boolean;
/** Whether selection can be placed after this node */
canInsertTextAfter(): boolean;
}
/**
* Check if node is a DecoratorNode
* @param node - Node to check
* @returns True if node is DecoratorNode
*/
function $isDecoratorNode(node: LexicalNode | null | undefined): node is DecoratorNode;Utility functions for working with nodes.
/**
* Build import map for node serialization
* @param nodes - Array of node classes or replacements
* @returns Map of node types to classes
*/
function buildImportMap(
nodes: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>
): Map<string, Klass<LexicalNode>>;
// Node-related type definitions
type Klass<T extends LexicalNode> = {
new (...args: any[]): T;
} & Omit<LexicalNode, keyof T>;
interface LexicalNodeReplacement {
replace: Klass<LexicalNode>;
with: <T extends LexicalNode>(node: LexicalNode) => T;
withKlass: Klass<LexicalNode>;
}
type NodeMap = Map<NodeKey, LexicalNode>;Usage Examples:
import {
$createParagraphNode,
$createTextNode,
$isParagraphNode,
$getRoot
} from "lexical";
// Create and structure nodes
const paragraph = $createParagraphNode();
const text = $createTextNode('Hello, world!');
const breakNode = $createLineBreakNode();
const moreText = $createTextNode('Second line');
// Build document structure
paragraph.append(text, breakNode, moreText);
// Get root and append
const root = $getRoot();
root.append(paragraph);
// Work with children
const children = paragraph.getChildren();
console.log('Number of children:', paragraph.getChildrenSize());
// Type checking and manipulation
if ($isParagraphNode(someNode)) {
someNode.setFormat('center');
someNode.setIndent(1);
}