{"id":13457655,"url":"https://github.com/facebook/lexical","last_synced_at":"2025-09-09T19:53:29.880Z","repository":{"id":36994911,"uuid":"318341089","full_name":"facebook/lexical","owner":"facebook","description":"Lexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.","archived":false,"fork":false,"pushed_at":"2025-05-05T03:01:41.000Z","size":82358,"stargazers_count":21129,"open_issues_count":492,"forks_count":1873,"subscribers_count":128,"default_branch":"main","last_synced_at":"2025-05-05T09:58:07.104Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://lexical.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/facebook.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-12-03T22:53:26.000Z","updated_at":"2025-05-05T09:37:23.000Z","dependencies_parsed_at":"2024-02-26T20:25:49.748Z","dependency_job_id":"70327655-e07c-4df3-a737-c4d7957c1efe","html_url":"https://github.com/facebook/lexical","commit_stats":{"total_commits":4120,"total_committers":434,"mean_commits":9.493087557603687,"dds":0.6393203883495145,"last_synced_commit":"e79c946c8b10fc33cef44b3247b6d87e2bdd12da"},"previous_names":["facebookexternal/outline","facebook/outline"],"tags_count":342,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/facebook%2Flexical","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/facebook%2Flexical/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/facebook%2Flexical/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/facebook%2Flexical/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/facebook","download_url":"https://codeload.github.com/facebook/lexical/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252486821,"owners_count":21755832,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T09:00:33.117Z","updated_at":"2025-05-05T12:28:57.953Z","avatar_url":"https://github.com/facebook.png","language":"TypeScript","readme":"\u003ch1 align=\"center\"\u003e\n  \u003ca href=\"https://lexical.dev\"\u003eLexical\u003c/a\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"GitHub Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/facebook/lexical/tests.yml\"/\u003e\n  \u003ca href=\"https://www.npmjs.com/package/lexical\"\u003e\n    \u003cimg alt=\"Visit the NPM page\" src=\"https://img.shields.io/npm/v/lexical\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://discord.gg/KmG4wQnnD9\"\u003e\n    \u003cimg alt=\"Add yourself to our Discord\" src=\"https://img.shields.io/discord/953974421008293909\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://x.com/intent/follow?screen_name=lexicaljs\"\u003e\n    \u003cimg alt=\"Follow us on X\" src=\"https://img.shields.io/twitter/follow/lexicaljs?style=social\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nLexical 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.\n\nFor documentation and more information about Lexical, be sure to [visit the Lexical website](https://lexical.dev).\n\nHere are some examples of what you can do with Lexical:\n\n- [Lexical Playground](https://playground.lexical.dev)\n- [Plain text sandbox](https://stackblitz.com/github/facebook/lexical/tree/main/examples/react-plain-text?embed=1\u0026file=src%2FApp.tsx\u0026terminalHeight=0\u0026ctl=1\u0026showSidebar=0\u0026devtoolsheight=0\u0026view=preview)\n- [Rich text sandbox](https://stackblitz.com/github/facebook/lexical/tree/main/examples/react-rich?embed=1\u0026file=src%2FApp.tsx\u0026terminalHeight=0\u0026ctl=1\u0026showSidebar=0\u0026devtoolsheight=0\u0026view=preview)\n\n\n---\n\n**Overview:**\n\n- [Getting started with React](#getting-started-with-react)\n\n- [Lexical is a framework](#lexical-is-a-framework)\n\n- [Working with Lexical](#working-with-lexical)\n\n- [Contributing to Lexical](#contributing-to-lexical)\n\n---\n\n## Getting started with React\n\n\u003e Note: Lexical is not only limited to React. Lexical can support any underlying DOM based library once bindings for that library have been created.\n\nInstall `lexical` and `@lexical/react`:\n\n```\nnpm install --save lexical @lexical/react\n```\n\nBelow is an example of a basic plain text editor using `lexical` and `@lexical/react` ([try it yourself](https://stackblitz.com/github/facebook/lexical/tree/main/examples/react-plain-text?embed=1\u0026file=src%2FApp.tsx\u0026terminalHeight=0\u0026ctl=1\u0026showSidebar=0\u0026devtoolsheight=0\u0026view=preview)).\n\n```jsx\nimport {$getRoot, $getSelection} from 'lexical';\nimport {useEffect} from 'react';\n\nimport {LexicalComposer} from '@lexical/react/LexicalComposer';\nimport {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';\nimport {ContentEditable} from '@lexical/react/LexicalContentEditable';\nimport {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';\nimport {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';\nimport {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';\nimport {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';\n\nconst theme = {\n  // Theme styling goes here\n  // ...\n}\n\n// When the editor changes, you can get notified via the\n// LexicalOnChangePlugin!\nfunction onChange(editorState) {\n  editorState.read(() =\u003e {\n    // Read the contents of the EditorState here.\n    const root = $getRoot();\n    const selection = $getSelection();\n\n    console.log(root, selection);\n  });\n}\n\n// Lexical React plugins are React components, which makes them\n// highly composable. Furthermore, you can lazy load plugins if\n// desired, so you don't pay the cost for plugins until you\n// actually use them.\nfunction MyCustomAutoFocusPlugin() {\n  const [editor] = useLexicalComposerContext();\n\n  useEffect(() =\u003e {\n    // Focus the editor when the effect fires!\n    editor.focus();\n  }, [editor]);\n\n  return null;\n}\n\n// Catch any errors that occur during Lexical updates and log them\n// or throw them as needed. If you don't throw them, Lexical will\n// try to recover gracefully without losing user data.\nfunction onError(error) {\n  console.error(error);\n}\n\nfunction Editor() {\n  const initialConfig = {\n    namespace: 'MyEditor',\n    theme,\n    onError,\n  };\n\n  return (\n    \u003cLexicalComposer initialConfig={initialConfig}\u003e\n      \u003cPlainTextPlugin\n        contentEditable={\n          \u003cContentEditable\n            aria-placeholder={'Enter some text...'}\n            placeholder={\u003cdiv\u003eEnter some text...\u003c/div\u003e}\n          /\u003e\n        }\n        ErrorBoundary={LexicalErrorBoundary}\n      /\u003e\n      \u003cOnChangePlugin onChange={onChange} /\u003e\n      \u003cHistoryPlugin /\u003e\n      \u003cMyCustomAutoFocusPlugin /\u003e\n    \u003c/LexicalComposer\u003e\n  );\n}\n```\n\n## Lexical is a framework\n\nThe core of Lexical is a dependency-free text editor framework that allows developers to build powerful, simple and complex,\neditor surfaces. Lexical has a few concepts that are worth exploring:\n\n### Editor instances\n\nEditor instances are the core thing that wires everything together. You can attach a contenteditable DOM element to editor instances, and also\nregister listeners and commands. Most importantly, the editor allows for updates to its `EditorState`. You can create an editor instance\nusing the `createEditor()` API, however you normally don't have to worry when using framework bindings such as `@lexical/react` as this\nis handled for you.\n\n### Editor States\n\nAn Editor State is the underlying data model that represents what you want to show on the DOM. Editor States contain two parts:\n\n- a Lexical node tree\n- a Lexical selection object\n\nEditor States are immutable once created, and in order to create one, you must do so via `editor.update(() =\u003e {...})`. However, you\ncan also \"hook\" into an existing update using node transforms or command handlers – which are invoked as part of an existing update\nworkflow to prevent cascading/waterfalling of updates. You can retrieve the current editor state using `editor.getEditorState()`.\n\nEditor States are also fully serializable to JSON and can easily be serialized back into the editor using `editor.parseEditorState()`.\n\n### Reading and Updating Editor State\n\nWhen you want to read and/or update the Lexical node tree, you must do it via `editor.update(() =\u003e {...})`. You may also do\nread-only operations with the editor state via `editor.read(() =\u003e {...})` or `editor.getEditorState().read(() =\u003e {...})`.\nThe closure passed to the update or read call is important, and must be synchronous. It's the only place where you have full\n\"lexical\" context of the active editor state, and providing you with access to the Editor State's node tree. We promote using\nthe convention of using `$` prefixed functions (such as `$getRoot()`) to convey that these functions must be called in this\ncontext. Attempting to use them outside of a read or update will trigger a runtime error.\n\nFor those familiar with React Hooks, you can think of these $functions as having similar functionality:\n| *Feature* | React Hooks | Lexical $functions |\n| -- | -- | -- |\n| Naming Convention | `useFunction` | `$function` |\n| Context Required | Can only be called while rendering | Can only be called while in an update or read |\n| Can be composed | Hooks can call other hooks | $functions can call other $functions |\n| Must be synchronous | ✅ | ✅ |\n| Other rules | ❌ Must be called unconditionally in the same order | ✅ None |\n\nNode Transforms and Command Listeners are called with an implicit `editor.update(() =\u003e {...})` context.\n\nIt is permitted to do nested updates, or nested reads, but an update should not be nested in a read\nor vice versa. For example, `editor.update(() =\u003e editor.update(() =\u003e {...}))` is allowed. It is permitted\nto nest an `editor.read` at the end of an `editor.update`, but this will immediately flush the update\nand any additional update in that callback will throw an error.\n\nAll Lexical Nodes are dependent on the associated Editor State. With few exceptions, you should only call methods\nand access properties of a Lexical Node while in a read or update call (just like `$` functions). Methods\non Lexical Nodes will first attempt to locate the latest (and possibly a writable) version of the node from the\nactive editor state using the node's unique key. All versions of a logical node have the same key. These keys\nare managed by the Editor, are only present at runtime (not serialized), and should be considered to be random and\nopaque (do not write tests that assume hard-coded values for keys).\n\nThis is done because the editor state's node tree is recursively frozen after reconciliation to\nsupport efficient time travel (undo/redo and similar use cases). Methods that update nodes\nfirst call `node.getWritable()`, which will create a writable clone of a frozen node. This would normally\nmean that any existing references (such as local variables) would refer to a stale version of the node, but\nhaving Lexical Nodes always refer to the editor state allows for a simpler and less error-prone data model.\n\n:::tip\n\nIf you use `editor.read(() =\u003e { /* callback */ })` it will first flush any pending updates, so you will\nalways see a consistent state. When you are in an `editor.update`, you will always be working with the\npending state, where node transforms and DOM reconciliation may not have run yet.\n`editor.getEditorState().read()` will use the latest reconciled `EditorState` (after any node transforms,\nDOM reconciliation, etc. have already run), any pending `editor.update` mutations will not yet be visible.\n\n:::\n\n### DOM Reconciler\n\nLexical has its own DOM reconciler that takes a set of Editor States (always the \"current\" and the \"pending\") and applies a \"diff\"\non 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,\nexcept Lexical is able to skip doing much of the diffing work, as it knows what was mutated in a given update. The DOM reconciler\nadopts performance optimizations that benefit the typical heuristics of a content editable – and is able to ensure consistency for\nLTR and RTL languages automatically.\n\n### Listeners, Node Transforms and Commands\n\nOutside of invoking updates, the bulk of work done with Lexical is via listeners, node transforms and commands. These all stem from\nthe 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:\n\n```js\nconst unregisterListener = editor.registerUpdateListener(({editorState}) =\u003e {\n  // An update has occurred!\n  console.log(editorState);\n});\n\n// Ensure we remove the listener later!\nunregisterListener();\n```\n\nCommands are the communication system used to wire everything together in Lexical. Custom commands can be created using `createCommand()` and\ndispatched to an editor using `editor.dispatchCommand(command, payload)`. Lexical dispatches commands internally when key presses are triggered\nand when other important signals occur. Commands can also be handled using `editor.registerCommand(handler, priority)`, and incoming commands are\npropagated through all handlers by priority until a handler stops the propagation (in a similar way to event propagation in the browser).\n\n## Working with Lexical\n\nThis section covers how to use Lexical, independently of any framework or library. For those intending to use Lexical in their React applications,\nit's advisable to [check out the source-code for the hooks that are shipped in `@lexical/react`](https://github.com/facebook/lexical/tree/main/packages/lexical-react/src).\n\n### Creating an editor and using it\n\nWhen you work with Lexical, you normally work with a single editor instance. An editor instance can be thought of as the one responsible\nfor wiring up an EditorState with the DOM. The editor is also the place where you can register custom nodes, add listeners, and transforms.\n\nAn editor instance can be created from the `lexical` package and accepts an optional configuration object that allows for theming and other options:\n\n```js\nimport {createEditor} from 'lexical';\n\nconst config = {\n  namespace: 'MyEditor',\n  theme: {\n    ...\n  },\n};\n\nconst editor = createEditor(config);\n```\n\nOnce you have an editor instance, when ready, you can associate the editor instance with a content editable `\u003cdiv\u003e` element in your document:\n\n```js\nconst contentEditableElement = document.getElementById('editor');\n\neditor.setRootElement(contentEditableElement);\n```\n\nIf you want to clear the editor instance from the element, you can pass `null`. Alternatively, you can switch to another element if need be,\njust pass an alternative element reference to `setRootElement()`.\n\n### Working with Editor States\n\nWith Lexical, the source of truth is not the DOM, but rather an underlying state model\nthat Lexical maintains and associates with an editor instance. You can get the latest\neditor state from an editor by calling `editor.getEditorState()`.\n\nEditor states are serializable to JSON, and the editor instance provides a useful method\nto deserialize stringified editor states.\n\n```js\nconst stringifiedEditorState = JSON.stringify(editor.getEditorState().toJSON());\n\nconst newEditorState = editor.parseEditorState(stringifiedEditorState);\n```\n\n### Updating an editor\n\nThere are a few ways to update an editor instance:\n\n- Trigger an update with `editor.update()`\n- Setting the editor state via `editor.setEditorState()`\n- Applying a change as part of an existing update via `editor.registerNodeTransform()`\n- Using a command listener with `editor.registerCommand(EXAMPLE_COMMAND, () =\u003e {...}, priority)`\n\nThe most common way to update the editor is to use `editor.update()`. Calling this function\nrequires a function to be passed in that will provide access to mutate the underlying\neditor state. When starting a fresh update, the current editor state is cloned and\nused as the starting point. From a technical perspective, this means that Lexical leverages a technique\ncalled double-buffering during updates. There's an editor state to represent what is current on\nthe screen, and another work-in-progress editor state that represents future changes.\n\nReconciling an update is typically an async process that allows Lexical to batch multiple synchronous\nupdates of the editor state together in a single update to the DOM – improving performance. When\nLexical is ready to commit the update to the DOM, the underlying mutations and changes in the update\nbatch will form a new immutable editor state. Calling `editor.getEditorState()` will then return the\nlatest editor state based on the changes from the update.\n\nHere's an example of how you can update an editor instance:\n\n```js\nimport {$getRoot, $getSelection, $createParagraphNode} from 'lexical';\n\n// Inside the `editor.update` you can use special $ prefixed helper functions.\n// These functions cannot be used outside the closure, and will error if you try.\n// (If you're familiar with React, you can imagine these to be a bit like using a hook\n// outside of a React function component).\neditor.update(() =\u003e {\n  // Get the RootNode from the EditorState\n  const root = $getRoot();\n\n  // Get the selection from the EditorState\n  const selection = $getSelection();\n\n  // Create a new ParagraphNode\n  const paragraphNode = $createParagraphNode();\n\n  // Create a new TextNode\n  const textNode = $createTextNode('Hello world');\n\n  // Append the text node to the paragraph\n  paragraphNode.append(textNode);\n\n  // Finally, append the paragraph to the root\n  root.append(paragraphNode);\n});\n```\n\nIf you want to know when the editor updates so you can react to the changes, you can add an update\nlistener to the editor, as shown below:\n\n```js\neditor.registerUpdateListener(({editorState}) =\u003e {\n  // The latest EditorState can be found as `editorState`.\n  // To read the contents of the EditorState, use the following API:\n\n  editorState.read(() =\u003e {\n    // Just like editor.update(), .read() expects a closure where you can use\n    // the $ prefixed helper functions.\n  });\n});\n```\n\n## Contributing to Lexical\n\nPlease read the [CONTRIBUTING.md](https://github.com/facebook/lexical/blob/main/CONTRIBUTING.md).\n\n### Optional but recommended, use VSCode for development\n\n1.  Download and install VSCode\n\n    - Download from [here](https://code.visualstudio.com/download) (it’s recommended to use the unmodified version)\n\n2.  Install extensions\n    - [Flow Language Support](https://marketplace.visualstudio.com/items?itemName=flowtype.flow-for-vscode)\n      - Make sure to follow the setup steps in the README\n    - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)\n      - Set prettier as the default formatter in `editor.defaultFormatter`\n      - Optional: set format on save `editor.formatOnSave`\n    - [ESlint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)\n\n## Documentation\n\n- [Getting started](https://lexical.dev/docs/intro)\n- [Concepts](https://lexical.dev/docs/concepts/editor-state)\n- [How Lexical was designed](https://lexical.dev/docs/design)\n- [Testing](https://lexical.dev/docs/testing)\n- [Maintainers' Guide](https://lexical.dev/docs/maintainers-guide)\n\n## Browser Support\n\n- Firefox 52+\n- Chrome 49+\n- Edge 79+ (when Edge switched to Chromium)\n- Safari 11+\n- iOS 11+ (Safari)\n- iPad OS 13+ (Safari)\n- Android Chrome 72+\n\nNote: Lexical does not support Internet Explorer or legacy versions of Edge.\n\n## Contributing\n\n1. Create a new branch\n   - `git checkout -b my-new-branch`\n2. Commit your changes\n   - `git commit -a -m 'Description of the changes'`\n     - There are many ways of doing this and this is just a suggestion\n3. Push your branch to GitHub\n   - `git push origin my-new-branch`\n4. Go to the repository page in GitHub and click on \"Compare \u0026 pull request\"\n   - The [GitHub CLI](https://cli.github.com/manual/gh_pr_create) allows you to skip the web interface for this step (and much more)\n\n## Support\n\nIf 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](https://discord.gg/KmG4wQnnD9).\n\nLexical engineers are checking this regularly.\n\n## Running tests\n\n- `npm run test-unit` runs only unit tests.\n- `npm run test-e2e-chromium` runs only chromium e2e tests.\n- `npm run debug-test-e2e-chromium` runs only chromium e2e tests in head mode for debugging.\n- `npm run test-e2e-firefox` runs only firefox e2e tests.\n- `npm run debug-test-e2e-firefox` runs only firefox e2e tests in head mode for debugging.\n- `npm run test-e2e-webkit` runs only webkit e2e tests.\n- `npm run debug-test-e2e-webkit` runs only webkit e2e tests in head mode for debugging.\n\n### License\n\nLexical is [MIT licensed](https://github.com/facebook/lexical/blob/main/LICENSE).\n","funding_links":[],"categories":["JavaScript","TypeScript","Rich text editor","编辑器","27. 富文本编辑器/Markdown编辑器/Markdown解析器"],"sub_categories":["网络服务_其他","24.3 Web Sockets"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffacebook%2Flexical","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffacebook%2Flexical","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffacebook%2Flexical/lists"}