Lexical
Note: Lexical is currently in early development and APIs and packages are likely to change quite often.
Lexical is an extensible JavaScript web text-editor framework with an emphasis on reliability, accessibility and performance. Lexical aims to provide a best-in-class developer experience, so you can easily prototype and build features with confidence. Combined with a highly extensible architecture, Lexical allows developers to create unique text editing experiences that scale in size and functionality.
For documentation and more information about Lexical, be sure to visit the Lexical website.
Here are some examples of what you can do with Lexical:
Getting started with React
Note: Lexical is not only limited to React. Lexical can support any underlying DOM based library once bindings for that library have been created.
Install lexical
and @lexical/react
:
npm install --save lexical @lexical/react
Below is an example of a basic plain text editor using lexical
and @lexical/react
(try it yourself).
import {$getRoot, $getSelection} from 'lexical';
import {useEffect} from 'react';
import LexicalComposer from '@lexical/react/LexicalComposer';
import LexicalPlainTextPlugin from '@lexical/react/LexicalPlainTextPlugin';
import LexicalContentEditable from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import LexicalOnChangePlugin from '@lexical/react/LexicalOnChangePlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
const theme = {
// Theme styling goes here
...
}
// When the editor changes, you can get notified via the
// LexicalOnChangePlugin!
function onChange(editorState) {
editorState.read(() => {
// Read the contents of the EditorState here.
const root = $getRoot();
const selection = $getSelection();
console.log(root, selection);
});
}
// Lexical React plugins are React components, which makes them
// highly composable. Furthermore, you can lazy load plugins if
// desired, so you don't pay the cost for plugins until you
// actually use them.
function MyCustomAutoFocusPlugin() {
const [editor] = useLexicalComposerContext();
useEffect(() => {
// Focus the editor when the effect fires!
editor.focus();
}, [editor]);
return null;
}
// Catch any errors that occur during Lexical updates and log them
// or throw them as needed. If you don't throw them, Lexical will
// try to recover gracefully without losing user data.
function onError(error) {
console.error(error);
}
function Editor() {
const initialConfig = {
theme,
onError,
};
return (
<LexicalComposer initialConfig={initialConfig}>
<LexicalPlainTextPlugin
contentEditable={<LexicalContentEditable />}
placeholder={<div>Enter some text...</div>}
/>
<LexicalOnChangePlugin onChange={onChange} />
<HistoryPlugin />
<MyCustomAutoFocusPlugin />
</LexicalComposer>
);
}
Lexical is a framework
The core of Lexical is a dependency-free text editor framework that allows developers to build powerful, simple and complex, editor surfaces. Lexical's underlying engine provides three main parts:
- editor instances that each attach to a single content editable element and act as a pub/sub for specific events and commands.
- a set of editor states that represent the current and pending states of the editor at any given time.
- a DOM reconciler that takes a set of editor states, diffs the changes, and updates the DOM according to their state.
By design, the core of Lexical tries to be as minimal as possible. Lexical doesn't directly concern itself with things that monolithic editors tend to do – such as UI components, toolbars or rich-text features and markdown. Instead the logic for those features can be included via a plugin interface and used as and when they're needed. This ensures great extensibilty and keeps code-sizes to a minimal – ensuring apps only pay the cost for what they actually import.
For React apps, Lexical has tight integration with React 18+ via the optional @lexical/react
package. This package provides production-ready utility functions, helpers and React hooks that make it seemless to create text editors within React.
Working with Lexical
This section covers how to use Lexical, independently of any framework or library. For those intending to use Lexical in their React applications, it's advisable to check out the source-code for the hooks that are shipped in @lexical/react
.
Creating an editor and using it
When you work with Lexical, you normally work with a single editor instance. An editor instance can be thought of as the one responsible for wiring up an EditorState with the DOM. The editor is also the place where you can register custom nodes, add listeners, and transforms.
An editor instance can be created from the lexical
package and accepts an optional configuration object that allows for theming and other options:
import {createEditor} from 'lexical';
const config = {
theme: {
...
},
};
const editor = createEditor(config);
Once you have an editor instance, when ready, you can associate the editor instance with a content editable If you want to clear the editor instance from the element, you can pass With Lexical, the source of truth is not the DOM, but rather an underlying state model that Lexical maintains and associates with an editor instance. You can get the latest editor state from an editor by calling Editor states have two phases: Editor states contain two core things: Editor states are serializable to JSON, and the editor instance provides a useful method to deserialize stringified editor states. There are a few ways to update an editor instance: The most common way to update the editor is to use Creating an update is typically an async process that allows Lexical to batch multiple updates together in a single update – improving performance. When Lexical is ready to commit the update to the DOM, the underlying mutations and changes in the update will form a new immutable editor state. Calling Here's an example of how you can update an editor instance: If you want to know when the editor updates so you can react to the changes, you can add an update listener to the editor, as shown below: Clone this repository Install dependencies Start local server and run tests Note: for collaboration, ensure you start the websocket server separately with Download and install VSCode Install extensions Note: Lexical does not support Internet Explorer or legacy versions of Edge. If you have any questions about Lexical, would like to discuss a bug report, or have questions about new integrations, feel free to add yourself to our Discord server. Lexical engineers are checking this regularly. Lexical is MIT licensed.const contentEditableElement = document.getElementById('editor');
editor.setRootElement(contentEditableElement);
null
. Alternatively, you can switch to another element if need be, just pass an alternative element reference to setRootElement()
.
Understanding the Editor State
editor.getEditorState()
.
const stringifiedEditorState = JSON.stringify(editor.getEditorState().toJSON());
const newEditorState = editor.parseEditorState(stringifiedEditorState);
Updating an editor
editor.update()
editor.setEditorState()
editor.registerNodeTransform()
editor.registerCommand(EXAMPLE_COMMAND, () => {...}, priority)
editor.update()
. Calling this function requires a function to be passed in that will provide access to mutate the underlying editor state. When starting a fresh update, the current editor state is cloned and used as the starting point. From a technical perspective, this means that Lexical leverages a technique called double-buffering during updates. There's an editor state to represent what is current on the screen, and another work-in-progress editor state that represents future changes.editor.getEditorState()
will then return the latest editor state based on the changes from the update.import {$getRoot, $getSelection, $createParagraphNode} from 'lexical';
// Inside the `editor.update` you can use special $ prefixed helper functions.
// These functions cannot be used outside the closure, and will error if you try.
// (If you're familiar with React, you can imagine these to be a bit like using a hook
// outside of a React function component).
editor.update(() => {
// Get the RootNode from the EditorState
const root = $getRoot();
// Get the selection from the EditorState
const selection = $getSelection();
// Create a new ParagraphNode
const paragraphNode = $createParagraphNode();
// Create a new TextNode
const textNode = $createTextNode('Hello world');
// Append the text node to the paragraph
paragraphNode.append(textNode);
// Finally, append the paragraph to the root
root.append(paragraphNode);
});
editor.registerUpdateListener(({editorState}) => {
// The latest EditorState can be found as `editorState`.
// To read the contents of the EditorState, use the following API:
editorState.read(() => {
// Just like editor.update(), .read() expects a closure where you can use
// the $ prefixed helper functions.
});
});
Creating custom Lexical nodes
Contributing to Lexical
npm install
npm run start
npm run test
npm run collab
.
Optional but recommended, use VSCode for development
editor.defaultFormatter
editor.formatOnSave
Documentation
Browser Support
Contributing
git checkout -b my-new-branch
git commit -a -m 'Description of the changes'
git push origin my-new-branch
Support
Running tests
npm run test-unit
runs only unit tests.npm run test-e2e:chromium
runs only chromium e2e tests.npm run debug-test-e2e:chromium
runs only chromium e2e tests in head mode for debugging.npm run test-e2e:firefox
runs only firefox e2e tests.npm run debug-test-e2e:firefox
runs only firefox e2e tests in head mode for debugging.npm run test-e2e:webkit
runs only webkit e2e tests.npm run debug-test-e2e:webkit
runs only webkit e2e tests in head mode for debugging.
License