Skip to main content

Introduction

Lexical is an extensible text editor framework for the web, built for reliability, accessibility, and performance. It gives you a small, dependency-free core and a set of optional packages that you compose into the editor you need, from a plain-text input with mentions to a collaborative rich-text document editor.

Lexical attaches to a contenteditable element and keeps its own model of the document. You read and change that model with Lexical's APIs, and Lexical takes care of keeping the DOM, the selection, and the browser's many contenteditable quirks in sync. Most code never touches the DOM directly; the main exception is custom nodes, which define how they render.

What can you build?​

  • Plain-text inputs that need more than a <textarea>: mentions, hashtags, links, custom emoji.
  • Rich-text editors for comments, posts, and messages.
  • Full document editors with tables, lists, code blocks, and images, for a CMS or a notes app.
  • Real-time collaborative editing with shared content and remote cursors, using the Yjs integration.

Lexical supplies the editing infrastructure. Your application supplies the layout, toolbars, menus, styling, and storage.

Lexical is an open-source community project. It began at Meta, and its contributors today include Meta engineers alongside many volunteers and developers from other companies. It powers text editing in Meta's web products and at Bloomberg, and is also the editor behind Payload CMS, Supabase, Proton Docs, MDXEditor, Sveltia CMS, Dify, RAGFlow, DeepSeek Harness, Paperclip, and Lexxy. To see what it can do, try the playground.

How it fits together​

The lexical package is the core: the editor, the editor state, the base node types, selection, commands, and the DOM reconciler. Everything else is an optional package built on top of it, so an application only includes the features it uses. The core is framework-agnostic, and @lexical/react provides React bindings.

Features are added to an editor as extensions. An extension bundles everything a feature needs (its nodes, configuration, commands, and listeners, plus any extensions it depends on) so it can be added in one place:

import {buildEditorFromExtensions} from '@lexical/extension';
import {HistoryExtension} from '@lexical/history';
import {RichTextExtension} from '@lexical/rich-text';
import {defineExtension} from 'lexical';

const editor = buildEditorFromExtensions(
defineExtension({
dependencies: [RichTextExtension, HistoryExtension],
name: '@my-app/editor',
namespace: 'my-app',
}),
);
editor.setRootElement(document.getElementById('editor'));

In React, LexicalExtensionComposer does the same job. The Quick Start and React guide walk through a complete setup.

Core concepts​

These are the ideas the rest of the documentation builds on. Each one links to a page with the details.

Editor​

The editor wires everything together. It owns the current editor state, attaches to a root DOM element, and is where you register nodes, listeners, transforms, and commands. You usually create it with buildEditorFromExtensions or through the React bindings rather than calling createEditor() yourself.

Editor state​

An editor state is an immutable snapshot of the document: a tree of nodes under a single RootNode, plus a selection. The editor state, not the DOM, is the source of truth. Its toJSON() output is how you save and restore content.

A DOM-like document tree​

Lexical's node tree is shaped like the HTML it renders. A paragraph contains its text, and a link is an element that contains the text it wraps. See Document Model, and Compared with ProseMirror for how this differs from a mark-based editor.

Reading and updating editor state​

All reads and writes happen inside a synchronous callback, such as editor.update(fn) or editor.read('force-commit', fn). Functions whose names start with $, such as $getRoot(), only work inside those callbacks, in the same way React Hooks only work while a component renders. See Editor State.

Updates and the DOM reconciler​

Updates change a pending copy of the editor state. Updates in the same tick are batched, and when the batch commits, the reconciler patches only the DOM that belongs to changed nodes. Lexical also watches for DOM changes made outside it and keeps or reverts them. Updates walks through each phase, with an interactive example.

Commands, transforms, and listeners​

Most editor behavior is built from three kinds of hooks. Commands carry user input and your own actions to handlers in priority order. Node transforms run during an update to keep the document in shape. Listeners react after an update is committed.

Serialization and running on a server​

Content converts to and from JSON, HTML, and Markdown; see Serialization. An editor with no root element does no DOM work at all, so the same code can run on a server or in tests; see Running Without a Browser.

Get started​