Convert Figma logo to code with AI

facebook logolexical

Lexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.

19,483
1,649
19,483
640

Top Related Projects

A browser based code editor

43,470

Quill is a modern WYSIWYG editor built for compatibility and extensibility

29,779

A completely customizable framework for building rich text editors. (Currently in beta.)

14,968

The world's #1 JavaScript library for rich text editing. Available for React, Vue and Angular

Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.

In-browser code editor (version 5, legacy)

Quick Overview

Lexical is an extensible text editor framework developed by Facebook. It provides a robust foundation for building rich text editing experiences in web applications, with a focus on performance, accessibility, and extensibility.

Pros

  • High performance and efficiency, even with large documents
  • Extensible architecture allowing for custom plugins and features
  • Strong focus on accessibility and internationalization
  • Lightweight core with modular design

Cons

  • Relatively new project, still evolving and may have some stability issues
  • Learning curve for developers unfamiliar with the architecture
  • Limited ecosystem compared to more established editors
  • Documentation could be more comprehensive for advanced use cases

Code Examples

  1. Basic Editor Setup:
import {$getRoot, $getSelection} from 'lexical';
import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';

function Editor() {
  const initialConfig = {
    namespace: 'MyEditor',
    onError: (error) => console.error(error),
  };

  return (
    <LexicalComposer initialConfig={initialConfig}>
      <PlainTextPlugin
        contentEditable={<ContentEditable />}
        placeholder={<div>Enter some text...</div>}
      />
      <HistoryPlugin />
    </LexicalComposer>
  );
}
  1. Custom Plugin Creation:
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';

function MyCustomPlugin() {
  const [editor] = useLexicalComposerContext();
  
  useEffect(() => {
    return editor.registerUpdateListener(({editorState}) => {
      editorState.read(() => {
        // Custom logic here
      });
    });
  }, [editor]);

  return null;
}
  1. Rich Text Formatting:
import {$getSelection, $isRangeSelection} from 'lexical';
import {FORMAT_TEXT_COMMAND} from 'lexical';

function formatText(formatType) {
  editor.update(() => {
    const selection = $getSelection();
    if ($isRangeSelection(selection)) {
      selection.formatText(formatType);
    }
  });
}

// Usage
<button onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')}>
  Bold
</button>

Getting Started

  1. Install Lexical and its React package:

    npm install lexical @lexical/react
    
  2. Create a basic editor component:

    import {LexicalComposer} from '@lexical/react/LexicalComposer';
    import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
    import {ContentEditable} from '@lexical/react/LexicalContentEditable';
    
    function Editor() {
      const initialConfig = {
        namespace: 'MyEditor',
        onError: (error) => console.error(error),
      };
    
      return (
        <LexicalComposer initialConfig={initialConfig}>
          <PlainTextPlugin
            contentEditable={<ContentEditable />}
            placeholder={<div>Enter some text...</div>}
          />
        </LexicalComposer>
      );
    }
    
  3. Use the editor component in your React application.

Competitor Comparisons

A browser based code editor

Pros of Monaco Editor

  • More mature and widely adopted, with extensive documentation and community support
  • Offers advanced features like IntelliSense, code folding, and multi-language support
  • Highly customizable with a rich set of APIs for extending functionality

Cons of Monaco Editor

  • Larger bundle size, which may impact load times for web applications
  • Steeper learning curve due to its extensive feature set and configuration options
  • Less suitable for simple text editing scenarios where a lightweight solution is preferred

Code Comparison

Monaco Editor:

import * as monaco from 'monaco-editor';

monaco.editor.create(document.getElementById('container'), {
    value: 'function hello() {\n\tconsole.log("Hello world!");\n}',
    language: 'javascript'
});

Lexical:

import {$getRoot, $createParagraphNode} from 'lexical';
import {LexicalComposer} from '@lexical/react/LexicalComposer';

const initialConfig = {
    namespace: 'MyEditor',
    onError: (error) => console.error(error),
};

Summary

Monaco Editor is a powerful, feature-rich code editor with extensive language support, making it ideal for complex development environments. Lexical, on the other hand, is a more lightweight and flexible text editing framework, better suited for content-focused applications and simpler editing scenarios. The choice between the two depends on the specific requirements of your project and the level of editing functionality needed.

43,470

Quill is a modern WYSIWYG editor built for compatibility and extensibility

Pros of Quill

  • More mature and established project with a larger community and ecosystem
  • Easier to get started with for simple use cases
  • Better documentation and examples available

Cons of Quill

  • Less flexible and extensible compared to Lexical's modular architecture
  • Performance may be slower for complex documents or large-scale applications
  • Limited support for collaborative editing features out of the box

Code Comparison

Quill:

var quill = new Quill('#editor', {
  theme: 'snow'
});
quill.on('text-change', function(delta, oldDelta, source) {
  console.log('Text changed!');
});

Lexical:

const editor = createEditor({
  theme: MyCustomTheme,
  onError: (error) => console.error(error),
});
editor.registerUpdateListener(({editorState}) => {
  editorState.read(() => {
    console.log('Editor updated!');
  });
});

Both Quill and Lexical are powerful rich text editors, but they have different approaches. Quill is more straightforward to implement for basic use cases, while Lexical offers greater flexibility and control over the editing experience. Lexical's modular architecture allows for more customization and better performance in complex scenarios, but it may require more setup and configuration. Quill's mature ecosystem and documentation make it easier for beginners to get started, but it may be less suitable for advanced use cases or large-scale applications.

29,779

A completely customizable framework for building rich text editors. (Currently in beta.)

Pros of Slate

  • More mature and established project with a larger community
  • Highly customizable and extensible architecture
  • Better documentation and examples

Cons of Slate

  • Steeper learning curve due to its flexibility
  • Larger bundle size compared to Lexical
  • Less frequent updates and maintenance

Code Comparison

Slate:

import { Editor } from 'slate-react'

const MyEditor = () => (
  <Editor
    value={initialValue}
    onChange={value => setValue(value)}
  />
)

Lexical:

import { LexicalComposer } from '@lexical/react/LexicalComposer'

const MyEditor = () => (
  <LexicalComposer initialConfig={editorConfig}>
    <PlainTextPlugin />
  </LexicalComposer>
)

Both Slate and Lexical are powerful rich text editing frameworks for React applications. Slate offers more flexibility and customization options, making it suitable for complex editing scenarios. However, this comes at the cost of a steeper learning curve and larger bundle size.

Lexical, being newer and developed by Facebook, focuses on simplicity and performance. It has a smaller footprint and is easier to get started with, but may lack some advanced features found in Slate.

Choose Slate for highly customized editing experiences, and Lexical for simpler, performance-focused implementations.

14,968

The world's #1 JavaScript library for rich text editing. Available for React, Vue and Angular

Pros of TinyMCE

  • Mature and widely adopted WYSIWYG editor with extensive documentation
  • Rich plugin ecosystem and customization options
  • Supports a wide range of browsers and platforms

Cons of TinyMCE

  • Larger file size and potentially slower performance
  • Steeper learning curve for advanced customizations
  • Less modern architecture compared to newer alternatives

Code Comparison

TinyMCE initialization:

tinymce.init({
  selector: '#myTextarea',
  plugins: 'link image table',
  toolbar: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright'
});

Lexical initialization:

const editor = new LexicalComposer({
  nodes: [HeadingNode, ListNode, ListItemNode],
  theme: {
    // Theme configuration
  },
});

TinyMCE focuses on configuration-based setup, while Lexical uses a more modular, component-based approach. TinyMCE offers a wider range of built-in features out of the box, whereas Lexical provides a more flexible foundation for custom editor experiences.

Lexical, being newer and developed by Facebook, employs modern React patterns and offers better performance for complex editing scenarios. However, TinyMCE's maturity and extensive plugin ecosystem make it a solid choice for projects requiring traditional rich text editing capabilities.

Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.

Pros of CKEditor 5

  • More mature and feature-rich, with a wider range of plugins and customization options
  • Better documentation and community support
  • Built-in accessibility features and compliance with WCAG 2.0 standards

Cons of CKEditor 5

  • Larger bundle size and potentially slower performance
  • Steeper learning curve for developers due to its complex architecture
  • Less flexibility in terms of core editor customization

Code Comparison

CKEditor 5:

import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

ClassicEditor
    .create(document.querySelector('#editor'))
    .then(editor => {
        console.log('Editor was initialized', editor);
    })
    .catch(error => {
        console.error(error);
    });

Lexical:

import {$getRoot, $createParagraphNode, $createTextNode} from 'lexical';

editor.update(() => {
  const root = $getRoot();
  const paragraph = $createParagraphNode();
  const text = $createTextNode('Hello world');
  paragraph.append(text);
  root.append(paragraph);
});

Both CKEditor 5 and Lexical are powerful rich text editors, but they cater to different needs. CKEditor 5 offers a more comprehensive out-of-the-box solution with extensive features and plugins, making it suitable for complex content creation scenarios. Lexical, on the other hand, provides a lightweight and flexible foundation for building custom editors, allowing developers more control over the core functionality and performance optimization.

In-browser code editor (version 5, legacy)

Pros of CodeMirror 5

  • Mature and widely adopted, with extensive documentation and community support
  • Highly customizable with a rich set of plugins and themes
  • Excellent performance for handling large documents

Cons of CodeMirror 5

  • Older architecture, not built with modern web technologies in mind
  • Less focus on collaborative editing features
  • Steeper learning curve for complex customizations

Code Comparison

Lexical:

import {$getRoot, $createParagraphNode, $createTextNode} from 'lexical';

editor.update(() => {
  const root = $getRoot();
  const paragraph = $createParagraphNode();
  paragraph.append($createTextNode('Hello world'));
  root.append(paragraph);
});

CodeMirror 5:

var editor = CodeMirror.fromTextArea(document.getElementById("editor"), {
  lineNumbers: true,
  mode: "javascript"
});

editor.setValue("Hello world");

Both Lexical and CodeMirror 5 are powerful text editing libraries, but they cater to different use cases. Lexical is designed for modern web applications with a focus on extensibility and collaborative editing, while CodeMirror 5 is a mature and feature-rich editor with excellent performance for traditional coding scenarios. The choice between them depends on specific project requirements and the desired balance between modern features and established reliability.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Lexical

GitHub Workflow Status Visit the NPM page Add yourself to our Discord Follow us on Twitter

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:


Overview:


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 {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';

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 = {
    namespace: 'MyEditor',
    theme,
    onError,
  };

  return (
    <LexicalComposer initialConfig={initialConfig}>
      <PlainTextPlugin
        contentEditable={<ContentEditable />}
        placeholder={<div>Enter some text...</div>}
        ErrorBoundary={LexicalErrorBoundary}
      />
      <OnChangePlugin 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 has a few concepts that are worth exploring:

Editor instances

Editor instances are the core thing that wires everything together. You can attach a contenteditable DOM element to editor instances, and also register listeners and commands. Most importantly, the editor allows for updates to its EditorState. You can create an editor instance using the createEditor() API, however you normally don't have to worry when using framework bindings such as @lexical/react as this is handled for you.

Editor States

An Editor State is the underlying data model that represents what you want to show on the DOM. Editor States contain two parts:

  • a Lexical node tree
  • a Lexical selection object

Editor States are immutable once created, and in order to create one, you must do so via editor.update(() => {...}). However, you can also "hook" into an existing update using node transforms or command handlers – which are invoked as part of an existing update workflow to prevent cascading/waterfalling of updates. You can retrieve the current editor state using editor.getEditorState().

Editor States are also fully serializable to JSON and can easily be serialized back into the editor using editor.parseEditorState().

Reading and Updating Editor State

When you want to read and/or update the Lexical node tree, you must do it via editor.update(() => {...}). You may also do read-only operations with the editor state via editor.read(() => {...}) or editor.getEditorState().read(() => {...}). The closure passed to the update or read call is important, and must be synchronous. It's the only place where you have full "lexical" context of the active editor state, and providing you with access to the Editor State's node tree. We promote using the convention of using $ prefixed functions (such as $getRoot()) to convey that these functions must be called in this context. Attempting to use them outside of a read or update will trigger a runtime error.

For those familiar with React Hooks, you can think of these $functions as having similar functionality:

FeatureReact HooksLexical $functions
Naming ConventionuseFunction$function
Context RequiredCan only be called while renderingCan only be called while in an update or read
Can be composedHooks can call other hooks$functions can call other $functions
Must be synchronous✅✅
Other rules❌ Must be called unconditionally in the same order✅ None

Node Transforms and Command Listeners are called with an implicit editor.update(() => {...}) context.

It is permitted to do nested updates, or nested reads, but an update should not be nested in a read or vice versa. For example, editor.update(() => editor.update(() => {...})) is allowed. It is permitted to nest nest an editor.read at the end of an editor.update, but this will immediately flush the update and any additional update in that callback will throw an error.

All Lexical Nodes are dependent on the associated Editor State. With few exceptions, you should only call methods and access properties of a Lexical Node while in a read or update call (just like $ functions). Methods on Lexical Nodes will first attempt to locate the latest (and possibly a writable) version of the node from the active editor state using the node's unique key. All versions of a logical node have the same key. These keys are managed by the Editor, are only present at runtime (not serialized), and should be considered to be random and opaque (do not write tests that assume hard-coded values for keys).

This is done because the editor state's node tree is recursively frozen after reconciliation to support efficient time travel (undo/redo and similar use cases). Methods that update nodes first call node.getWritable(), which will create a writable clone of a frozen node. This would normally mean that any existing references (such as local variables) would refer to a stale version of the node, but having Lexical Nodes always refer to the editor state allows for a simpler and less error-prone data model.

:::tip

If you use editor.read(() => { /* callback */ }) it will first flush any pending updates, so you will always see a consistent state. When you are in an editor.update, you will always be working with the pending state, where node transforms and DOM reconciliation may not have run yet. editor.getEditorState().read() will use the latest reconciled EditorState (after any node transforms, DOM reconciliation, etc. have already run), any pending editor.update mutations will not yet be visible.

:::

DOM Reconciler

Lexical has its own DOM reconciler that takes a set of Editor States (always the "current" and the "pending") and applies a "diff" on them. It then uses this diff to update only the parts of the DOM that need changing. You can think of this as a kind-of virtual DOM, except Lexical is able to skip doing much of the diffing work, as it knows what was mutated in a given update. The DOM reconciler adopts performance optimizations that benefit the typical heuristics of a content editable – and is able to ensure consistency for LTR and RTL languages automatically.

Listeners, Node Transforms and Commands

Outside of invoking updates, the bulk of work done with Lexical is via listeners, node transforms and commands. These all stem from the editor and are prefixed with register. Another important feature is that all the register methods return a function to easily unsubscribe them. For example here is how you listen to an update to a Lexical editor:

const unregisterListener = editor.registerUpdateListener(({editorState}) => {
  // An update has occurred!
  console.log(editorState);
});

// Ensure we remove the listener later!
unregisterListener();

Commands are the communication system used to wire everything together in Lexical. Custom commands can be created using createCommand() and dispatched to an editor using editor.dispatchCommand(command, payload). Lexical dispatches commands internally when key presses are triggered and when other important signals occur. Commands can also be handled using editor.registerCommand(handler, priority), and incoming commands are propagated through all handlers by priority until a handler stops the propagation (in a similar way to event propagation in the browser).

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 = {
  namespace: 'MyEditor',
  theme: {
    ...
  },
};

const editor = createEditor(config);

Once you have an editor instance, when ready, you can associate the editor instance with a content editable <div> element in your document:

const contentEditableElement = document.getElementById('editor');

editor.setRootElement(contentEditableElement);

If you want to clear the editor instance from the element, you can pass null. Alternatively, you can switch to another element if need be, just pass an alternative element reference to setRootElement().

Working with Editor States

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.getEditorState().

Editor states are serializable to JSON, and the editor instance provides a useful method to deserialize stringified editor states.

const stringifiedEditorState = JSON.stringify(editor.getEditorState().toJSON());

const newEditorState = editor.parseEditorState(stringifiedEditorState);

Updating an editor

There are a few ways to update an editor instance:

  • Trigger an update with editor.update()
  • Setting the editor state via editor.setEditorState()
  • Applying a change as part of an existing update via editor.registerNodeTransform()
  • Using a command listener with editor.registerCommand(EXAMPLE_COMMAND, () => {...}, priority)

The most common way to update the editor is to use 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.

Reconciling an update is typically an async process that allows Lexical to batch multiple synchronous updates of the editor state together in a single update to the DOM – improving performance. When Lexical is ready to commit the update to the DOM, the underlying mutations and changes in the update batch will form a new immutable editor state. Calling editor.getEditorState() will then return the latest editor state based on the changes from the update.

Here's an example of how you can update an editor instance:

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);
});

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:

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.
  });
});

Contributing to Lexical

Please read the CONTRIBUTING.md.

Optional but recommended, use VSCode for development

  1. Download and install VSCode

    • Download from here (it’s recommended to use the unmodified version)
  2. Install extensions

    • Flow Language Support
      • Make sure to follow the setup steps in the README
    • Prettier
      • Set prettier as the default formatter in editor.defaultFormatter
      • Optional: set format on save editor.formatOnSave
    • ESlint

Documentation

Browser Support

  • Firefox 52+
  • Chrome 49+
  • Edge 79+ (when Edge switched to Chromium)
  • Safari 11+
  • iOS 11+ (Safari)
  • iPad OS 13+ (Safari)
  • Android Chrome 72+

Note: Lexical does not support Internet Explorer or legacy versions of Edge.

Contributing

  1. Create a new branch
    • git checkout -b my-new-branch
  2. Commit your changes
    • git commit -a -m 'Description of the changes'
      • There are many ways of doing this and this is just a suggestion
  3. Push your branch to GitHub
    • git push origin my-new-branch
  4. Go to the repository page in GitHub and click on "Compare & pull request"
    • The GitHub CLI allows you to skip the web interface for this step (and much more)

Support

If you have any questions about Lexical, would like to discuss a bug report, or have questions about new integrations, feel free to join us at our Discord server.

Lexical engineers are checking this regularly.

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

Lexical is MIT licensed.

NPM DownloadsLast 30 Days