Skip to main content

Compared with ProseMirror

Lexical and ProseMirror are both frameworks for building rich text editors on top of contentEditable, and both keep an immutable document model as the source of truth. This page collects the main differences in one place, for readers who know one and are learning the other.

Document model​

Lexical's tree is shaped like the HTML it renders (see Document Model). This is a deliberate difference from ProseMirror. ProseMirror's document is also a tree, and blocks can contain other blocks, but the inline content of a textblock is a flat sequence of nodes (text and inline nodes) with marks for formatting and links, and every position in the document is a single integer.

LexicalProseMirror
DocumentImmutable tree of nodes, one RootNodeImmutable tree of nodes, one doc node
Node identity and navigationEach node has a runtime key and knows its parent and siblingsNodes are plain values with no parent links; a resolved position supplies ancestor context
BlocksElementNode subclasses (paragraph, heading, list, table, …), or a block DecoratorNode (image, embed, …)Block nodes defined in the schema
Inline formattingFormat flags on each TextNode (bold, italic, code, …)Marks on text
Links and other inline wrappersInline ElementNodes (LinkNode, MarkNode) that contain text nodesMarks on text, like formatting
Embedded contentDecoratorNode, inline or block, rendered by your framework (for example React)Leaf or atom nodes, often with a custom NodeView
Several editable regions in one nodeNamed slots: regions addressed by name, like a card's title, each isolated so editing and selection never cross the boundaryChild nodes in the order the schema's content expression allows, optionally marked isolating, or a separate editor inside a NodeView
Addressing a positionNode key plus offset ({key, offset, type})One integer counted across the whole document
Allowed structureDeclared by node classes, enforced with node transforms and normalizationDeclared by a schema of content expressions
Applying editsCall node and selection methods inside editor.update()Build a transaction from steps that address positions or ranges
RenderingEach node's createDOM()/updateDOM(), applied by the reconciler, and optionally changed per node class by a DOMRenderExtension overridetoDOM in the schema, or a NodeView

Serialization​

Because a Lexical link is an element that already contains its text, it exports as an <a> around those text nodes. A mark-based model has to find the adjacent text that shares a mark and group it into one element first.

FormatLexicalProseMirror
JSONeditorState.toJSON(), restored with editor.parseEditorState()doc.toJSON() and Node.fromJSON(schema, json)
HTML export$generateHtmlFromNodes(), using each node's createDOM() or exportDOM()DOMSerializer.fromSchema(schema), using toDOM from the schema's nodes and marks
HTML import$generateNodesFromDOM(), using importDOM() or DOMImportExtension rulesDOMParser.fromSchema(schema), using the schema's parseDOM rules
Markdown@lexical/markdown transformers or @lexical/mdastprosemirror-markdown: a markdown-it parser and a serializer with a function per node and mark
ClipboardCopy writes plain text, HTML, and Lexical JSON. Paste prefers the JSON, then HTML, then plain text.Copy writes HTML and plain text. Paste parses the HTML with the schema's parse rules.

In the JSON, a Lexical link is a link node with its text nodes as children, and bold is a format value on a text node, where ProseMirror's JSON gives each text node a list of marks. In both editors the default HTML export reuses the definition that renders the editor (createDOM() in Lexical, toDOM in ProseMirror), so the two stay in step unless you override one. See Serialization for the details of each Lexical format.

History​

Both editors group consecutive edits into one undo step. Lexical's HistoryExtension merges edits made within delay (300ms by default) and keeps an unlimited stack unless you set maxDepth. ProseMirror's history plugin uses newGroupDelay: 500 and depth: 100 by default, so configExtension(HistoryExtension, {delay: 500, maxDepth: 100}) matches its behavior.