facebook/lexical
 Watch   
 Star   
 Fork   
27 days ago
lexical

v0.49.0

v0.49.0 is a maintenance release headlined by the completion of the $config() protocol migration for Lexical's built-in node classes (#8640), a breaking change that replaces per-node getType()/clone()/importJSON()/importDOM()/transform() boilerplate with runtime-synthesized behavior. It ships with a cluster of follow-up fixes hardening the synthesized getType() and clone() (including a stack overflow in compiled builds), an opt-in sticky horizontal scrollbar for overflowing tables, and a broad batch of fixes across tables, Markdown, HTML, code highlighting, and selection.

Breaking Changes

  • lexical — Built-in node classes are ported to the $config() protocol. The static importJSON(), importDOM(), clone(), and transform() methods are no longer present on ported nodes; use the higher-level equivalents instead (LexicalEditor.parseEditorState/$generateNodesFromDOM, the $cloneWithProperties helper, and extensions or $transform in $config). getType() is unchanged and safe to keep using, __type is now readonly, and node constructors require zero-argument defaults. Custom nodes may keep their static methods, but adopting $config() is recommended (#8640)
  • lexicalLexicalCommand<T>'s payload type is now invariant to prevent unsafe structural compatibility between commands. Previously correct code (including explicit generics) needs no changes; only unsafe LexicalCommand<unknown> usage must switch to the newly exported AnyLexicalCommand alias. dispatchCommand's payload argument is now optional when a command's payload type is undefined or void, and redundant explicit type parameters were dropped from the registerCommand/registerNodeTransform call sites (#8877)

New APIs & Features

  • @lexical/table — Opt-in sticky horizontal scrollbar for tables wider than their container, so the scrollbar stays anchored at the viewport bottom instead of only being reachable at the end of the table. Enable it with hasStickyScrollbar: true in the table config; style it via the new tableStickyScrollbar theme key (#8790)
  • @lexical/table — The <colgroup> element is now omitted from the table DOM when column widths are undefined, producing cleaner markup and letting the browser auto-size columns; it is created or removed dynamically as widths change (#8850)
  • lexical — Added an editor operation benchmark suite (paragraph split, bold formatting, range deletion, paste, select-all format) measured through full DOM reconciliation, to help contributors track core editing performance (#8856)

Deprecations

  • @lexical/table$createTableSelection is deprecated in favor of $createTableSelectionFrom, which takes the table node plus anchor/focus cells and validates them, instead of returning a blank selection with placeholder 'root' keys that must be overwritten (#8855)
  • @lexical/tablegetShape() is deprecated because it computes incorrect bounds for merged cells; use the now-exported $computeTableCellRectBoundary (with $computeTableMap) instead (#8853)

Notable Fixes

$config() follow-ups (#8640)

  • The synthesized clone() now applies afterCloneFrom() when called directly (e.g. NodeClass.clone(node)), fixing silent property loss across 25+ core nodes (#8864)
  • The synthesized getType() no longer returns the superclass type when inherited, fixing node-type collisions during editor registration (#8867)
  • The synthesized getType() no longer recurses infinitely when a bundler copies it onto a subclass as an own static, fixing stack overflows in compiled/minified builds (#8869)

Tables

  • DELETE_LINE_COMMAND (Cmd/Ctrl-based delete-line shortcuts) now works inside table cells instead of being silently swallowed (#8851)
  • Table alignment now works when cells are selected in any direction, not just top-left→bottom-right (#8883)

Selection & editing

  • Editor updates dispatched from a read-only context (e.g. inside editor.read()) now run in a fresh writable update instead of being silently dropped, with a dev warning (#8863)
  • Firefox now creates a selection and shows the block cursor when clicking in the gap between block decorators (#8862)
  • Pressing ArrowUp before a leading non-inline decorator no longer moves the selection out of the editor (#8887)

Markdown, HTML & code

  • Typing a list marker at the start of a heading no longer converts the heading into a list (#8879)
  • HTML import now evaluates unrestricted CSS selector groups (e.g. p, .foo) against all elements instead of only tag-matched ones (#8873)
  • Concurrent async language/theme loads in the Shiki highlighter are deduplicated and merged into a single history entry, avoiding spurious undo states (#8854)
  • QuoteNode.updateDOM now accepts the EditorConfig argument the reconciler passes to every other node, for API consistency (#8882)

Playground & website

  • Playground scroll padding now accounts for the sticky toolbar so selections scrolled into view from above aren't hidden behind it (#8849)
  • Fixed a homepage crash caused by a minified dev build, and isolated each embedded example in its own error boundary so one failure no longer takes down the page (#8861)

What's Changed

  • [lexical] Bug Fix: Scope bench vitest projects to exclude regular test files by @mayrang in #8852
  • [lexical-table] Bug Fix: Enable DELETE_LINE_COMMAND in table cells by @mayrang in #8851
  • [lexical-table] Chore: Deprecate $createTableSelection in favor of $createTableSelectionFrom by @mayrang in #8855
  • [Breaking Changes][lexical] Refactor: Port node classes to the $config() protocol by @etrepum in #8640
  • [lexical-playground] Bug Fix: account for sticky toolbar in scroll padding by @etrepum in #8849
  • [lexical-list] Chore: Rename isNestedListNode to $isNestedListNode by @etrepum in #8843
  • [lexical-table][lexical-playground] Chore: Deprecate getShape() and migrate playground by @mayrang in #8853
  • [lexical-table] Chore: Remove dead code in table command handlers by @mayrang in #8857
  • [lexical-code-shiki] Bug Fix: Deduplicate async loads and merge into history by @Alxs39 in #8854
  • [lexical-table][lexical] Feature: Sticky scrollbar for overflowing tables by @mayrang in #8790
  • fix: $config() auto-synthesized clone() loses properties when called directly (#8640) by @potatowagon in #8864
  • fix: $config() synthesized getType() inherited by subclasses causes node-type collision (#8640) by @potatowagon in #8867
  • [lexical] Chore: Migrate LexicalSelection tests to buildEditorFromExtensions by @mayrang in #8865
  • [lexical] Feature: Editor operation benchmarks by @mayrang in #8856
  • [lexical] Bug Fix: run editor updates dispatched from a read-only context in a fresh writable update by @potatowagon in #8863
  • [lexical][lexical-website] Bug Fix: Homepage crash from optimized dev build by @etrepum in #8861
  • fix: $config() synthesized getType() recurses infinitely when inherited as own static (#8867 follow-up) by @potatowagon in #8869
  • [lexical] Bug Fix: Create selection when clicking between block decorators on Firefox by @mayrang in #8862
  • [lexical-table] Feature: Skip colgroup element in table DOM with undefined col widths by @zacharyg-qualtrics in #8850
  • [lexical-list] Chore: Remove redundant decorator-adjacent backspace handler by @mayrang in #8871
  • [lexical] Refactor: Simplify RangeSelection.insertText via removeText decomposition by @mayrang in #8870
  • [lexical] Performance: Skip redundant selection restoration in $removeTextFromCaretRange by @mayrang in #8872
  • [lexical-playground] Bug Fix: Deterministic history coalescing for flaky webkit undo tests by @etrepum in #8874
  • [lexical-html] Bug Fix: Dispatch unrestricted CSS selector groups by @winklemad in #8873
  • [Breaking Change][lexical] Chore: Remove redundant registerCommand/registerNodeTransform generics by @mayrang in #8877
  • [lexical-table] Bug Fix: Table alignment fails when selecting in non-TL->BR direction by @sahiee-dev in #8883
  • [lexical-rich-text] Bug Fix: Forward editorConfig to QuoteNode.updateDOM by @werterhalimi in #8882
  • [lexical-markdown] Bug Fix: Preserve headings when typing list shortcuts by @StevenPDang in #8879
  • [lexical-rich-text] Bug Fix: Keep block cursor in editor when ArrowUp at start of root (#8886) by @etrepum in #8887

New Contributors

Full Changelog: v0.48.0...v0.49.0

2026-07-17 02:24:41
lexical

v0.48.0

v0.48.0 is a maintenance release focused on bug fixes across Markdown, tables, lists, links, and selection. It's headlined by a fix for a v0.46.0 regression that broke native text drag-and-drop (#8842) and a couple of notable security hardening fixes. It also adds a handful of new features, including an MdastHtmlExtension with examples for authoring custom Markdown constructs (collapsibles, kbd, alerts, footnotes), a customizable Yjs shared-type root name for collaborative editing, and new table row manipulation helpers.

New APIs & Features

Notable Fixes

Drag & drop (fix for v0.46.0 regression)

  • Don't cancel dragover for text drags, so native drops work again (#8842)

Security

  • LinkNode.sanitizeUrl() now fails closed on unparseable URLs, preventing a potential XSS vector (#8846)
  • Fixed a serialize-javascript dependency vulnerability (#8803)

Markdown & code

  • Roundtrip overlapping inline formats correctly through mdast/Markdown (#8825)
  • Force re-tokenization after an async language load so highlighting appears once the grammar is ready (#8830)

Tables

  • Auto-scroll while drag-selecting cells past the visible edge (#8822)
  • Enable table copy in read-only mode (#8845)

Lists & character limit

  • Backspace at the start of a list item now outdents or converts to a paragraph (#8829)
  • Merge adjacent OverflowNodes in useCharacterLimit (#8831)
  • Count block separators when wrapping character-limit overflow (#8840)

Links & selection

  • Disable link opening for disabled autolinks (#8839)
  • Skip scrollIntoViewIfNeeded when the selection rect is above the editor, fixing a Safari RTL caret jump (#8848)

What's Changed

New Contributors

Full Changelog: https://github.com/facebook/lexical/compare/v0.47.0...v0.48.0

2026-07-10 03:12:19
lexical

v0.47.0

v0.47.0 is a monthly release with a lot of bug fixes, particularly related to IME and composition, and is headlined by two new experimental packages:

  • @lexical/mdast (#8794) — a micromark/mdast-based alternative to @lexical/markdown. Parsing, serialization, and markdown shortcuts all go through the same parser used by remark, so CommonMark + GFM compliance comes for free and there is no second grammar to keep in sync. The original syntax of each construct is preserved on the nodes, so re-serializing produces minimally different Markdown. @lexical/markdown remains the supported default for production apps. See the new Markdown with @lexical/mdast guide.
  • @lexical/a11y (#8591) — framework-agnostic accessibility helpers (ARIA live regions, focus management, roving tab index, focus trap) usable from React, Svelte, Vue, Solid, or vanilla DOM, with React wrappers shipping from @lexical/react. The playground adopts them as part of a WCAG AA reference pass, and the keyboard contracts Lexical follows are documented in the new Keyboard Accessibility guide.

Beyond the headliners: React 18 is now the baseline (see Breaking Changes), the playground gains Find and Replace and a Ruby annotation node with a floating editor, tables learn to spread pasted TSV across cells and get row/column header utilities, and there is a long list of IME/composition, selection, and navigation fixes.

Special recognition once again goes to @mayrang who has been plowing through IME, accessibility, and other input related issues as well as the really cool new Find and Replace and Ruby Annotations playground features.

Breaking Changes

@lexical/react / @lexical/devtools-core — React 17 support dropped (#8782)

React 18 is now the minimum supported version; the react / react-dom peerDependencies require >=18, and React 17 compatibility code paths were removed. If you are already on React 18 or 19, no changes are required.

lexical — DOM element preserved when composing on a segmented TextNode (#8784)

Starting IME composition in the middle of a segmented TextNode (e.g. a mention) previously replaced the node — destroying its DOM element and breaking the browser's composition tracking (caret flashing and styling bleed on Chrome). The node now temporarily switches to normal mode for the duration of composition, keeping the same node key and DOM element, and is cleaned up when composition ends. Subclass transforms or method overrides that assume a segmented node is always in segmented mode must account for this transient state — see the updated TextNode.setMode() documentation.

New APIs

  • lexical$getDocument() returns the owner document of the active editor (correct inside a Shadow DOM or iframe), falling back to the global document when there is no active editor; a new @lexical/eslint-plugin rule enforces its use over the document global (#8788, #8813)
  • lexicalregisterEventListener / registerEventListeners, type-safe addEventListener helpers that return cleanup functions, now used throughout the codebase — see the updated Working with DOM Events guide (#8767)
  • lexicalSET_TEXT_FORMAT_COMMAND sets (rather than toggles) the selection's text format (see Commands); the same PR fixes formatText toggle direction (#8807)
  • @lexical/markdown$generateNodesFromMarkdownString parses a markdown string into Lexical nodes without inserting them into the document or modifying the selection (#8789)
  • @lexical/table$setTableRowIsHeader and $setTableColumnIsHeader utilities (#8815); $insertTableRowAtNode and $insertTableColumnAtNode are now exported (#8791)

Notable Fixes

IME, composition & mobile

  • Prevent text duplication on iOS Safari when formatting during composition (#8755)
  • Skip ZWSP insertion for format mismatch on Android Chrome (#8769); deduplicate speech-to-text insertion on Android Chrome (#8759)
  • Event module globals moved into per-editor InputState (#8809); refactored IME composition test infrastructure with browser-level coverage (#8793)

Selection & navigation

  • ArrowUp/Down no longer skip block decorators (#8775); deleteLine no longer removes adjacent block decorators (#8744)
  • Keep selection inside single-child inline elements (#8772); fix navigation across unmergeable TextNode boundaries in inline-grid containers (#8797)
  • $setBlocksType properly handles block-end focus in backward selections (#8753); insertNodes re-resolves a detached firstBlock (#8764)
  • Place the block cursor between decorators and shadow roots (#8758); clean up the trailing shadow root after select-all delete (#8751)
  • deleteCharacter no longer overwrites the X11 PRIMARY selection via Selection.modify (#8774)
  • Defer onUpdate callbacks during nested commits (#8672)

Markdown, code & tables

  • Fence-like lines inside a code block stay content (#8734); prevent formatting on TabNode inside code blocks (#8752)
  • Pasted TSV text spreads across table cells (#8780)

Playground & website

  • Find and Replace (#8779) and Ruby annotation node with floating editor (#8741)
  • Support Unicode URLs in the autolink matcher (#8787); restore RTL checkbox position in checklists (#8783); forced-colors (high contrast) icon fixes (#8781, #8773)
  • Rewritten testing guide on the website (#8811)

What's Changed

  • [lexical][lexical-rich-text][lexical-selection] Bug Fix: Fix navigation across unmergeable TextNode boundaries in inline-grid containers (#8797) by @mayrang
  • [lexical][lexical-rich-text] Bug Fix: Fix formatText toggle direction and add SET_TEXT_FORMAT_COMMAND (#8807) by @mayrang
  • [scripts] Bug Fix: Let npm prompt for OTP when publishing bootstrap stubs (#8820) by @etrepum
  • [lexical-playground] Bug Fix: Clear inline font-size when converting to heading (#8800) by @mayrang
  • [lexical-table][lexical-playground] Feature: $setTableRowIsHeader and $setTableColumnIsHeader utilities (#8815) by @mayrang
  • [lexical-website] Documentation Update: Rewrite testing guide (#8811) by @mayrang
  • [lexical-mdast][lexical-rich-text] Feature: @lexical/mdast, a micromark/mdast-based alternative to @lexical/markdown (#8794) by @etrepum
  • Make dependency-check resilient to transient registry errors (#8818) by @zurfyx
  • [lexical] Refactor: Move event module globals into per-editor InputState (#8809) by @mayrang
  • [lexical] Bug Fix: $getDocument() should fall back to the global document when there is no active editor (#8813) by @potatowagon
  • [lexical-playground] Bug Fix: Keep cell background color modal open on first click (#8806) by @sahiee-dev
  • [lexical-playground] Bug Fix: Use consistent default maxWidth for markdown-imported images (#8810) by @mayrang
  • [*][lexical-devtools][lexical-playground] Chore: Update flow, hermes, and babel packages to latest (#8795) by @etrepum
  • Add a 7-day pnpm minimumReleaseAge to match the Dependabot cooldown (#8808) by @zurfyx
  • [lexical] Chore: Fix tmp package dependency vulnerability (#8802) by @vijayojha89
  • [lexical] Chore: Add missing Flow type declarations (#8799) by @mayrang
  • [lexical-markdown] Feature: Add $generateNodesFromMarkdownString (#8789) by @mayrang
  • [lexical][lexical-playground] Chore: Refactor IME composition test infrastructure and add browser-level coverage (#8793) by @mayrang
  • [lexical-playground] Bug Fix: Use viewBox dimensions for unsized Excalidraw output (#8798) by @mayrang
  • [lexical-table] Bug Fix: Export $insertTableRowAtNode and $insertTableColumnAtNode (#8791) by @hamo-o
  • [lexical-playground][lexical-website] Feature: Add Vercel Analytics and Speed Insights (#8796) by @zurfyx
  • [lexical][lexical-eslint-plugin] Feature: Add $getDocument() API and Shadow DOM lint enforcement (#8788) by @mayrang
  • [scripts] Bug Fix: strip misplaced pure annotations from prod builds (#8786) by @etrepum
  • [lexical] Bug Fix: deleteCharacter overwrites X11 PRIMARY selection via Selection.modify (#8774) by @etrepum
  • [lexical-playground] Bug Fix: Support Unicode URLs in autolink matcher (#8787) by @mayrang
  • [lexical-table] Feature: Spread pasted TSV text across table cells (#8780) by @mayrang
  • [Breaking Change][lexical] Bug Fix: Preserve DOM element when composing on segmented TextNode middle (#8784) by @mayrang
  • [Breaking Change][lexical-react][lexical-devtools-core] Chore: Drop React 17 support, baseline is now React 18 (#8782) by @etrepum
  • [lexical-playground][lexical] Feature: Ruby annotation node with floating editor (#8741) by @mayrang
  • [lexical-playground] Feature: Find and Replace (#8779) by @mayrang
  • [lexical-playground] Bug Fix: restore RTL checkbox position in checklist (#8783) by @ryanda9910
  • [lexical-rich-text] Bug Fix: Stop ArrowUp/Down from skipping block decorators (#8775) by @mayrang
  • [lexical-playground] Bug Fix: Convert remaining icon glyphs to mask-image for forced-colors support (#8781) by @mayrang
  • fix(lexical): use extends for type parameter bound in registerEventListeners flow type (#8778) by @potatowagon
  • [lexical] Bug Fix: Keep selection inside single-child inline elements (#8772) by @mayrang
  • [lexical][*] Feature: registerEventListener / registerEventListeners DOM helpers (#8767) by @etrepum
  • [lexical-playground] Bug Fix: Restore floating toolbar comment icon after mask-image conversion (#8773) by @mayrang
  • [lexical] Bug Fix: Skip ZWSP insertion for format mismatch on Android Chrome (#8769) by @mayrang
  • [lexical-a11y][lexical-react][lexical-playground][lexical-website] Feature: @lexical/a11y framework-agnostic accessibility helpers + WCAG AA reference adoption (#8591) by @mayrang
  • Add missing copyright headers to 4 source files (#8768) by @xiezhenjia-meta
  • [lexical-website] Bug Fix: Remove horizontal scroll on homepage hero (#8760) by @kainulla
  • [lexical-selection] Bug Fix: re-resolve detached firstBlock in insertNodes (#8764) by @ochevallier
  • [lexical] Modernize InlineFormattableNode Flow stub syntax rejected by fb-www flow strict (#8763) by @potatowagon
  • [lexical-utils] Bug Fix: positionNodeOnRange leaking orphan rect nodes when rects shrink (#8762) by @durvesh1992
  • docs: fix duplicated words in comments and docs (#8761) by @durvesh1992
  • [lexical-selection] Bug Fix: Properly handle block end focus in backward selections during $setBlocksType (#8753) by @ochevallier
  • [lexical][lexical-extension] Bug Fix: Align DecoratorTextNode format with TextNode in mixed selections (#8737) by @sahiee-dev
  • [lexical] Bug Fix: Clean up trailing shadow root after select-all delete (#8751) by @mayrang
  • [lexical][lexical-rich-text] Bug Fix: Place block cursor between decorators and shadow roots (#8758) by @mayrang
  • [lexical-playground] Bug Fix: Deduplicate speech-to-text insertion on Android Chrome (#8759) by @mayrang
  • [lexical] Bug Fix: Prevent deleteLine from removing adjacent block decorators (#8744) by @mayrang
  • build(deps): bump actions/checkout from 6 to 7 in the github-actions group (#8757) by @dependabot
  • [ci] Add internal-registry dependency availability check (#8756) by @zurfyx
  • [lexical] Bug Fix: Prevent text duplication on iOS Safari when formatting during composition (#8755) by @mayrang
  • [lexical-code][lexical-playground] Bug Fix: Prevent formatting on TabNode inside code blocks (#8752) by @mayrang
  • [lexical] Bug Fix: Defer onUpdate callbacks during nested commits (#8672) by @Rohithmatham12
  • [lexical-extension][lexical-playground] Chore: Some cleanups (#8732) by @levensta
  • [lexical-markdown] Bug Fix: keep fence-like lines inside a code block as content (#8734) by @GermanJablo
  • v0.46.0 (#8748) by @etrepum

New Contributors

  • @Rohithmatham12 made their first contribution in #8672
  • @kainulla made their first contribution in #8760
  • @durvesh1992 made their first contribution in #8761
  • @ochevallier made their first contribution in #8753
  • @ryanda9910 made their first contribution in #8783
  • @hamo-o made their first contribution in #8791
  • @vijayojha89 made their first contribution in #8802

Full Changelog: https://lexical.dev is the project site; compare view: https://github.com/facebook/lexical/compare/v0.46.0...v0.47.0

2026-06-26 03:34:55
lexical

v0.46.0

v0.46.0 is a monthly release headlined by two major new experimental capabilities:

  • Named slots - a model that lets a single host element or decorator node own several isolated editable regions inside the host's own editorState
  • Shadow DOM - the root element can now be hosted in an open shadow root (or iframe)

This release also includes many other fixes and new features across IME/composition, collaboration, markdown round-tripping, lists, code blocks, links, and mobile keyboards. It is also our first release (other than nightlies) to use NPM Trusted Publishing.

Special recognition for this release goes to @mayrang for doing the majority of the work on both of these new features as well as fixing some tricky IME/composition issues 👏 @levensta has also been doing a fantastic job going through the issue backlog, providing valuable feedback on these bleeding edge features, and identifying long-standing edge cases.

Breaking Changes

lexical — Node traversal methods no longer accept unsafe type parameters (#8661)

The zero-argument generics on getParent(OrThrow), getPreviousSibling(s), getNextSibling(s), getChildren, getFirstChild(OrThrow), getLastChild(OrThrow), getChildAtIndex, getFirstDescendant, getLastDescendant, and getDescendantByIndex were implicit unchecked casts — there is no inference site, so any non-base type argument was equivalent to an as cast. Each method is now an overload pair: a documented non-generic signature returning the base type (LexicalNode / ElementNode | null), plus the old generic signature marked @deprecated. Code that leaned on contextual-type inference no longer type-checks:

// Before: compiled (unsound). After: type error.
const node: ParagraphNode | null = $getRoot().getFirstChild();

// Port directly with an explicit cast (no behavior change):
const node = $getRoot().getFirstChild() as ParagraphNode | null;

// Or, better, narrow with a guard:
const node = $getRoot().getFirstChild();
if ($isParagraphNode(node)) { /* node: ParagraphNode */ }

Existing getFirstChild<ParagraphNode>()-style calls still compile but are now deprecated and will be removed in a future release. This is a types-only change with no runtime impact.

lexical$getNearestNodeFromDOMNode(rootElement) now returns RootNode (#8588)

The "selection captured outside of Lexical" mechanism is generalized beyond DecoratorNode subtrees: setDOMUnmanaged(dom, {captureSelection: true}) now marks any subtree (e.g. a DOMRenderExtension override or a getDOMSlot widget) as selection-captured, and isDOMCapturingSelection(dom) walks ancestors so a descendant <input> reports as captured. As part of this, the root element now carries a __lexicalKey_* stash, so $getNodeFromDOM / $getNearestNodeFromDOMNode resolve the root element to the RootNode instead of null. Two call sites become more correct (drop-on-root in clipboard, table-selection→range conversion), but external callers that relied on $getNearestNodeFromDOMNode(rootElement) returning null must update. The internal-only $isSelectionCapturedInDecorator was removed.

lexicalinsertNodes preserves a leading linebreak; managed <br>s are now tagged (#8615)

RangeSelection.insertNodes now preserves the first LineBreakNode when inserting inline content ahead of a block element, instead of silently dropping it. Separately, the reconciler-inserted "managed" line breaks (the otherwise-invisible <br>s Lexical adds so the caret can land in empty lines) are now identifiable in the DOM as <br data-lexical-managed-linebreak="true"> — and, on iOS Safari, an analogous <img data-lexical-managed-linebreak="true" …> in some cases. DOM/HTML snapshot test expectations may need to ignore or expect this attribute. Closes #3980.

@lexical/html and node extensions — DOMImportExtension rules now register implicitly (#8662) (experimental)

The experimental DOM-import pipeline no longer requires you to list a per-package import extension. Each node-providing extension (RichTextExtension, ListExtension, LinkExtension, TableExtension, CodeExtension) now registers its own import rules, and the standalone RichTextImportExtension / ListImportExtension / LinkImportExtension / TableImportExtension / CodeImportExtension / HorizontalRuleImportExtension become deprecated aliases that may be removed as early as v0.47.0. Rules stay inert unless HTML is routed through ClipboardDOMImportExtension or $generateNodesFromDOMViaExtension; the legacy importDOM paste path is unchanged. If you had not adopted these v0.45.0 extensions yet, there is no breaking change.

lexical / @lexical/yjs / @lexical/clipboard / @lexical/html — Named slots (#8603) (experimental)

All slot machinery is opt-in and gated (an editor latches _slotsUsed on the first $setSlot), so editors that never use slots take identical code paths to before. Two changes are observable regardless:

  • syncLexicalUpdateToYjsV2__EXPERIMENTAL takes a new dirtyLeaves parameter, inserted between dirtyElements and normalizedNodes (a slot host's values surface as dirty leaves).
  • A NodeSelection containing an ElementNode now includes that element's children on copy/export ($getHtmlContent / $generateJSONFromSelectedNodes). The previous behavior serialized a childless shell, making cut of an element NodeSelection silently lossy; partial RangeSelections keep per-child slicing and excludeFromCopy children remain excluded.

@lexical/link / @lexical/list / @lexical/react — Removed exports deprecated since v0.32.1 (#8704)

Exports marked @deprecated in v0.32.1 (2025-06-04, >12 months ago) with no Meta-internal consumers are removed:

  • @lexical/link: toggleLink → use $toggleLink
  • @lexical/list: insertList / removeList → use $insertList / $removeList (from an update or command listener)
  • @lexical/react: the ContentEditable Props type alias → use ContentEditableProps

Note: lexical's $nodesOfType is intentionally un-deprecated (it still has many consumers). The remaining v0.32.1 deprecations that still have internal consumers (KEY_MODIFIER_COMMAND, $wrapNodes, the @lexical/table row/column helpers, etc.) are left for a follow-up.

New APIs

lexicaleditor.read(mode, fn) and EditorReadMode (#8702)

read gains an optional first argument: 'force-commit' (the default; flushes pending updates first — the previous one-argument behavior), 'pending' (reads the pending state without flushing — the old editor.readPending, now removed), and 'latest' (reads the committed state without flushing, equivalent to editor.getEditorState().read(fn, {editor})). EditorReadMode is exported. readPending only shipped after v0.45.0, so its removal is not considered a breaking change.

lexical — DOM shadow root support (#8694)

An editor can now mount inside an open shadow tree without losing selection, focus, drag-and-drop, IME composition, or floating-UI behavior. Reads go through platform APIs — Selection.getComposedRanges, Selection.direction, ShadowRoot.activeElement, Document.caretPositionFromPoint(x, y, {shadowRoots}) — and nine new helpers ship from lexical; the light-DOM code paths are unchanged. Follow-ups #8708 (insert nodes at the block cursor inside a shadow root) and #8740 (ignore beforeinput/input in captured decorators, fixing Firefox 152) complete the surface.

lexicalonWarn editor hook (#8644)

CreateEditorArgs gains an optional onWarn?: ErrorHandler (stored as _onWarn, defaulting to console.warn), mirroring onError. The infinite-update-loop guard's recoverable warning now routes through it (wired end-to-end in #8658), so embedders that bridge reporting to their own telemetry no longer lose this signal to each user's browser console.

lexical$config() accessor-based nominal typing (#8645)

An additive extension of the $config() protocol: abstract base classes can declare configuration shared with their subclasses under a Symbol.for(<ClassName>) key (resolved by getStaticNodeConfig), and accessor-based nominal typing lets subclasses opt into stricter $config() checks. No existing node class is changed.

@lexical/history — Cut/paste get their own undo entry (#8649)

Any update tagged PASTE_TAG or CUT_TAG is now classified as an OTHER change inside @lexical/history, which keeps it from merging into the preceding keystrokes and keeps following keystrokes from merging into it. Undoing a short paste (or an iOS autocorrect/prediction) or a cut no longer also undoes the text you typed immediately before it.

@lexical/extensionSelectBlockExtension (#8532)

Overrides SELECT_ALL_COMMAND so the first invocation selects the nearest block element and the second selects the entire document; a selection already spanning multiple blocks expands straight to the document, and select-all is a no-op when everything is already selected. Includes an opt-in cascadeSelection option for nested editors (image captions, etc.) and enables PreventSelectAllExtension by default to keep keydown from leaking out of input/textarea elements. (Also introduced editor.read('pending', …) via the now-generalized #8702.)

@lexical/yjs / @lexical/react — Collaborative cursors via the CSS Custom Highlight API (#8550)

Remote collaborators' selections are now painted with the widely-supported CSS Custom Highlight API; the error-prone absolutely-positioned overlay remains only as a fallback. The remote caret is still rendered as a positioned element. Closes #4457, #5837.

@lexical/utilsdedupeSelectionRects (#8709)

A new exported helper that drops zero-area rects and any rect that contains another (with 1px tolerance) from Range.getClientRects(), fixing the duplicate/over-bright and spurious extra-wide fake-selection rects WebKit produces on some blocks. It is wired into positionNodeOnRange, so every fake-selection consumer (markSelection, selectionAlwaysOnDisplay, the extension) gets clean rects. Addresses #7106, #7492.

@lexical/code-coreescapeWithArrows (#8393)

CodeIndentExtension gains an escapeWithArrows option (default false): when a code block is the terminal node and the caret is at the end of its text, pressing the arrow keys creates a new paragraph and moves the caret into it. New $onEscapeDown / $onEscapeUp helpers back it in @lexical/utils. Closes #7912, #4685, #5968.

@lexical/react — Self-contained LexicalErrorBoundary (#8720)

LexicalErrorBoundary no longer wraps react-error-boundary (the dependency is dropped) and gains an optional fallback prop — omit it for the default message, or pass fallback={null} to render nothing. Its public type is unchanged, so it remains usable as the ErrorBoundary prop of RichTextPlugin / PlainTextPlugin.

Highlights

Core (lexical):

  • ⚠️ #8661 Deprecate unsafe type parameters on node traversal methods
  • ⚠️ #8588 $getNearestNodeFromDOMNode(rootElement) resolves to RootNode; generalized captured selection via setDOMUnmanaged({captureSelection})
  • ⚠️ #8615 Preserve the first linebreak in insertNodes; tag managed <br> with data-lexical-managed-linebreak
  • 🆕 #8694 DOM shadow root support via platform selection APIs
  • 🆕 #8702 editor.read(mode, fn) overload + EditorReadMode (replaces readPending)
  • 🆕 #8644 onWarn editor hook (#8658 routes the recursion guard through it end-to-end)
  • 🆕 #8645 $config() accessor-based nominal typing
  • ✅ #8598 TabNode.setTextContent for Safari IME composition
  • ✅ #8604 Caret stuck when a block has no leading or trailing text
  • ✅ #8680 Emit COMPOSITION_END_TAG from the Firefox onInput defer branch
  • ✅ #8701 Recheck text-node contents on deletion with composition
  • ✅ #8686 Reuse the empty trailing block when typing at root + last-offset selection
  • ✅ #8708 Insert nodes at the block cursor inside a shadow root
  • ✅ #8715 Normalize non-inline nodes when inserting into inline-only parents
  • ✅ #8740 Ignore beforeinput/input in captured decorators (Firefox 152)
  • ✅ #8635 #8631 #8612 #8638 Stop the infinite-update-loop detector over-firing on fast typing; report via devInvariant
  • ✅ #8617 $assumeEditor warns via devInvariant instead of throwing in prod
  • ✅ #8726 Guard the klass.prototype null check in getStaticNodeConfig
  • ✅ #8739 Use an inheritsLoose-safe helper for class-inheritance loops
  • ✅ #8593 Harden against ReDoS and prototype pollution (linear-time regexes, prototype guards); faster CSS parsing
  • 🧹 #8628 Surface a clear error when TypeScript (<5.2) can't read the package exports
  • 🧹 #8579 Consolidate tokenization through tokenizeRawText / $generateNodesFromRawText
  • 🧹 #8735 Better error messages for invalid node classes; cache getStaticNodeConfig
  • 🧹 #8675 #8742 #8667 T[] over Array<T>, modernized Flow stubs, dropped deprecated traversal type params in tests

Named slots (experimental):

  • ⚠️🆕 #8603 Named slots — a host node owns multiple isolated editable regions inside its own editorState
  • ✅ #8716 Named-slot typing / Backspace / Copy / hydrate paths

HTML / DOM import:

  • ⚠️ #8662 Register DOMImportExtension rules implicitly via node extensions; make tree-shaking annotations effective
  • 🧹 #8590 Migrate the playground's HTML import/export to the DOMImportExtension pipeline
  • ✅ #8589 $generateHtmlFromNodes self-establishes active-editor scope (back-compat for #8519)

Extension / History:

  • 🆕 #8532 SelectBlockExtension
  • 🆕 #8649 Snapshot history before cut/paste so an undo doesn't swallow prior typing

Code:

  • 🆕 #8393 escapeWithArrows: create a paragraph around a terminal code block with the arrow keys
  • 🆕 #8600 Add Go to the code-language options
  • ✅ #8606 Set the unsupported-syntax flag only once (shiki)

Link / List:

  • ✅ #8513 Skip link wrapping on paste for non-simple text nodes
  • ✅ #8705 Preserve the LinkNode wrap on copy in Firefox/Safari
  • ✅ #8676 Preserve a previous DecoratorNode on Backspace at the start of a top-level list

Markdown:

  • ✅ #8688 Code spans bind tighter than text-match transformers
  • ✅ #8723 Inline code spans containing backticks
  • ✅ #8728 Preserve inline formatting when wrapping already-formatted text with matching markers
  • ✅ #8678 Update the ordered-list start when typing a marker before it
  • ✅ #8562 Preserve block equation markdown

Mark / Clipboard / Table:

  • 🧹 #8717 Widen MarkNode method return types to boolean (restores subclass overrides)
  • ✅ #8706 Correct clipboard type-checking
  • ✅ #8674 Don't throw on stale table node keys in $handleTableSelectionChangeCommand

Collaboration (@lexical/yjs) / Dragon:

  • 🆕 #8550 Render collab cursors via the CSS Custom Highlight API
  • ✅ #8611 Local range selection no longer grows when a collaborator edits
  • ✅ #8652 Keep the collab cursor at element end when decoding an out-of-range position
  • ✅ #8651 #6614 Avoid empty-paragraph echo and a splice crash on collab undo; don't preserve tags on non-dirty updates
  • ✅ #8646 Fix Yjs desync after clearing all nodes
  • ✅ #8665 @lexical/dragon: handle makeChanges messages and the listener-registration race

Utils / React:

  • 🆕 #8709 dedupeSelectionRects — clean up WebKit fake-selection rects
  • 🧹 #8733 Move getScrollParent to @lexical/utils
  • ✅ #8684 Fix double paragraph creation when exiting a nested code block
  • 🧹 #8720 Self-contained LexicalErrorBoundary (drops react-error-boundary) with an optional fallback prop
  • 🧹 #8682 Hook syntax in .js.flow files; 📝 #8714 API doc coverage of @lexical/react exports

Rich Text / Plain Text:

  • ✅ #8725 Refresh the iOS keyboard suggestion bar after Backspace for all locales
  • ✅ #8663 Call preventDefault() in dragover for HTML5 DnD compliance

Playground:

  • 🆕 #8574 Korean IME autocomplete with composition-idle ghost text (pluggable AutocompleteDictionary)
  • 🆕 #8594 Non-printing marks (, , , ·), toggled from Settings
  • ✅ #8599 Autocomplete wordlist returns the highest-priority completion
  • ✅ #8666 Clear block alignment/indent on a collapsed (but not partial) selection
  • ✅ #8719 Render PageBreakNode as <hr> so Safari fires beforeinput after paste
  • ✅ #8655 Fix the importmap of the /esm/ proof of concept
  • ✅ #8695 Correct the CodeBlock layout-exit e2e expected HTML
  • 🧹 #8585 #8595 #8637 Audit and de-flake the e2e suite (remove all @flaky tags)
  • 🧹 #8639 Remove the localhost:1235 validation-server code from ActionsPlugin

Infrastructure / Tooling:

  • 🧹 #8587 #8597 #8747 Consolidate release workflows and adopt npm trusted publishing
  • 🧹 #8614 Vitest browser-mode tests via the Playwright runner
  • 🧹 #8673 Fix intermittent EACCES in Windows CI browser tests
  • 🧹 #8703 Migrate editor.getEditorState().read(...) to editor.read('latest', ...)
  • 🧹 #8634 Require all changes to be backwards compatible (AGENTS.md / CLAUDE.md)
  • 🧹 #8693 Remove the unused tmp dependency; #8647 replace the node-state-style example with the dev-examples version
  • 🧹 Dependency bumps: #8566 #8573 #8618 #8619 #8621 #8622 #8624 #8625 #8626 #8629 #8630 #8632 #8633

What's Changed

New Contributors

Full Changelog: https://github.com/facebook/lexical/compare/v0.45.0...v0.46.0

2026-05-29 02:49:37
lexical

v0.45.0

v0.45.0 is a monthly release with several breaking changes around selection and reconcile semantics, a major new experimental DOMImportExtension API for composable HTML import that replaces importDOM, a generalized DOMSlot and DOM render override surface, new HistoryExtension ergonomics (canUndo/canRedo signals, maxDepth), notable reconciler/selection performance work, and a wide range of bug fixes across IME, mobile keyboards, list/code/table editing, and markdown round-tripping.

Breaking Changes

lexical — Shadow root inclusion in $getReconciledDirection (#8479)

$getReconciledDirection now walks through shadow roots when resolving the reconciled dir attribute on a node, so nested ShadowRoot-style elements (e.g. tables) inherit direction consistently with regular elements. Callers that relied on direction inheritance stopping at shadow roots will need to update.

lexical / @lexical/rich-text — Backspace at block start preserves the current block (#8493)

Pressing Backspace at the start of a non-empty block previously merged the current block into the previous block, which discarded the current block's type/format. The new behavior merges the previous block's children into the current block instead, preserving the current block's type (heading, list item, quote, etc.). A new $mergeBlockBackward helper on RangeSelection is exposed for custom command handlers that want the same behavior.

lexical — Selection adjustment in removeFromParent callers (#8501)

LexicalNode.replace, insertBefore, insertAfter, and related callers that move a node between parents now correctly adjust the current selection to follow the moved node instead of leaving the selection pointing at the (now empty) original slot. This may change selection state observed by transforms or update listeners that previously relied on the stale offsets.

@lexical/markdown — Apply markdown shortcuts on composition-committed triggers (#8503)

Markdown shortcuts (e.g. *, _, ~) typed via IME composition now trigger after the composition is committed, not just on raw beforeinput. This fixes shortcuts being silently dropped during Japanese / Korean / Chinese input but may also fire shortcuts in some flows that previously didn't.

@lexical/extension / @lexical/rich-text / @lexical/plain-textNormalizeInlineElementsExtension (#8497)

registerRichText and registerPlainText now register a transform that removes empty inline elements (e.g. LinkNode, formatted TextNode wrappers with no children) instead of leaving them in the tree. The transform is also exported as NormalizeInlineElementsExtension from @lexical/extension for use with extension-based setups. Editors that intentionally kept empty inline wrappers will need to disable the extension or override the transform. See Included Extensions.

lexical / @lexical/extension / @lexical/rich-text / @lexical/plain-textNormalizeTripleClickSelectionExtension (#8520)

The "triple-click selects the whole paragraph including the trailing newline" normalization has moved out of LexicalEvents and into a new NormalizeTripleClickSelectionExtension registered by both rich-text and plain-text. The core LexicalEditor no longer performs this normalization on its own, so editors that bypass registerRichText / registerPlainText (or the equivalent extensions) need to register the extension explicitly to keep the previous behavior. See Included Extensions.

lexical / @lexical/html / @lexical/selection / @lexical/utils / @lexical/playground — Generalized DOMSlot and DOMRenderExtension override surface (#8519)

DOMSlot is now generalized so child slots can be expressed for arbitrary DOM containers (not just the direct child element), and $createDOM, $updateDOM, $exportDOM, $getDOMSlot etc. all flow through the new override surface. The previously-exported AutocompleteNode in the playground has been replaced by a VisibleLineBreakExtension that demonstrates the new override surface, and markSelection in @lexical/utils has been rewritten on top of the generalized slot API. Custom nodes that override getDOMSlot to return a non-element wrapper now have a real way to express that. See the DOMRenderExtension docs.

New APIs

@lexical/htmlDOMImportExtension (experimental) (#8528)

A new DOMImportExtension replaces the legacy node-class importDOM static method with a composable middleware-style API. Rules declare a CSS-selector-style matcher, an optional schema for child handling, and a DOMImportFn that builds Lexical nodes from the matching DOM. Many helpers ship alongside it: $distributeInlineWrapper, ImportSession, DOMPreprocessFn for source-specific cleanup (Word, VS Code, etc.), and full TypeScript types for rules, captures, and contexts. A new dev-examples/dom-import showcase wires it up to a Word/VS Code paste flow. The legacy importDOM continues to work; new code can opt in to DOMImportExtension for richer composition and explicit ordering. The companion ClipboardDOMImportExtension (in @lexical/clipboard) routes text/html pastes through the new pipeline. See the DOMImportExtension guide and the "Migrating from importDOM" section.

@lexical/html / @lexical/playground — Conditional DOM render overrides (#8575)

domOverride now supports disabledForEditor and disabledForSession predicates so render overrides can be conditionally skipped on a per-editor or per-session basis (e.g. only apply during export, or only when a feature flag is on). Useful when one extension provides multiple overrides whose applicability depends on context that isn't known at registration time. See the DOMRenderExtension docs.

lexicalElementNode import/export of data-lexical-indent (#8536)

ElementNode now uniformly imports and exports the data-lexical-indent attribute so any subclass round-trips its indent level through HTML without having to override exportDOM / importDOM (fixes the long-standing #7729 regression around indented headings/paragraphs).

lexical — Infinite recursion detection in update listeners (#8542)

Update listeners and update transforms that re-trigger themselves are now caught by a runtime guard that throws a descriptive error after a configurable number of nested updates, instead of hanging the tab. See Updates and Listeners.

@lexical/historycanUndo / canRedo ReadonlySignals (#8465)

HistoryExtension's output now exposes canUndo and canRedo as ReadonlySignal<boolean>s so toolbar buttons can subscribe directly without polling historyState or duplicating the priority/state bookkeeping. See Extension Signals.

@lexical/historymaxDepth option (#8537)

HistoryExtension accepts a new maxDepth option that bounds the undo stack. Older entries are evicted FIFO when the depth is exceeded. See the new "Tuning HistoryExtension for memory and long sessions" docs.

@lexical/markdown$convertSelectionToMarkdownString (#8395)

Counterpart to $convertToMarkdownString that serializes only the current selection (or any caller-provided range) to a markdown string, useful for "copy as markdown" toolbar actions and for selection-scoped AI prompts.

lexical / @lexical/list$setFormatFromDOM (#8460)

A new $setFormatFromDOM helper extracts text-align / dir from a DOM element and applies it to an ElementNode. ListItemNode.importDOM now uses it to correctly import alignment for list items pasted from external sources.

@lexical/listListNode.createListItemNode (#8427)

ListNode exposes a createListItemNode factory that is now used by children-normalization, so ListNode subclasses can supply a custom ListItemNode subclass without monkey-patching the normalizer.

@lexical/rich-textescapeFormatTriggers on RichTextExtension (#8383)

RichTextExtension now accepts an escapeFormatTriggers config (a ReadonlySignal<EscapeFormatTriggerConfig>) that opts text-format escape behavior in/out per trigger (enter, click, arrow, space, tab). With it enabled, typing at the boundary of a formatted text node "escapes" the active format (bold, italic, code, etc.), matching the typing experience in Google Docs / Notion. Disabled by default. See Included Extensions.

@lexical/extension / @lexical/playgroundClickAfterLastBlockExtension (#8549)

New ClickAfterLastBlockExtension inserts a fresh paragraph when the user clicks below the last block (and places the caret in it), so single-block editors and short documents don't strand the user with no easy way to start a new line.

@lexical/code-prism / @lexical/code-shikinull Tokenizer.defaultLanguage (#8553)

Tokenizer.defaultLanguage may now be null to indicate that code blocks without an explicit language should round-trip through markdown without injecting a default language tag (e.g. ```javascript). Preserves CommonMark's "no fence info" form on export.

@lexical/code-core / @lexical/code-shiki / @lexical/code-prism — Outdent space-indented code lines (#8445)

Shift+Tab in a code block now correctly outdents space-indented lines (not just tab-indented), respecting the tokenizer's configured indent width.

@lexical/react — Optional async onClose for LexicalTypeaheadMenuPlugin (#8489)

LexicalTypeaheadMenuPlugin's onClose may now return a Promise, useful for handlers that need to await network or animation work before tearing down the menu.

@lexical/clipboardGetClipboardDataExtension export (#8431)

GetClipboardDataExtension is now exported from @lexical/clipboard for use as a peer dependency from custom clipboard handlers.

@lexical/website — Server-rendered "Copy page" Markdown button (#8570)

Every docs page now has a server-rendered "Copy page as Markdown" button (and a matching ?raw URL) so AI assistants and humans can grab the raw markdown without scraping the rendered HTML.

Highlights

Core:

  • ⚠️ #8479 Include shadow root in $getReconciledDirection
  • ⚠️ #8493 Backspace at block start now preserves the current block (heading / list item / quote)
  • ⚠️ #8501 Adjust selection when LexicalNode callers move a node out of its parent
  • ⚠️ #8519 Generalize DOMSlot and add DOMRenderExtension override surface; markSelection rewritten on top
  • ⚠️ #8520 Move triple-click selection normalization to NormalizeTripleClickSelectionExtension
  • 🆕 #8536 ElementNode import/export of data-lexical-indent
  • 🆕 #8542 Detect infinite recursion in update listeners and transforms
  • ⚡ #8422 Defer DOM Selection property reads in $updateDOMSelection
  • ⚡ #8474 Cache RangeSelection.isBackward() on the instance
  • ⚡ #8481 Adopt GenMap copy-on-write for NodeMap and reconciler keyToDOMMap
  • ⚡ #8482 Children fast path with suffix-incremental cache update in $reconcileChildren
  • ⚡ #8505 Centralize replace-area selection mapping + bulk splice across list/selection/link
  • ⚡ #8548 Cache last-child kind for trailing-<br> reconcile
  • ✅ #8412 Import dir attribute in importDOM
  • ✅ #8417 Workarounds for buggy browser behavior around macOS text replacements
  • ✅ #8428 Respect CSS display style in isBlockDomNode / isInlineDomNode
  • ✅ #8441 Reuse DOM when reconciling cross-parent node moves
  • ✅ #8466 Resolve --lexical-indent-base-value via CSS var() instead of pre-computing in JS
  • ✅ #8470 Preserve queued updates and tags through setRootElement
  • ✅ #8475 Handle iOS 10-key Korean IME deleteContentBackward with targetRange
  • ✅ #8486 Keep caret above the on-screen keyboard after Enter
  • ✅ #8517 Handle triple-click overselection in $setBlocksType
  • ✅ #8547 Migrate away from legacy Flow syntax in flow definitions
  • ✅ #8558 Cursor stuck before leading inline DecoratorNode
  • ✅ #8564 Skip $reconcileChildren fast path during full reconcile
  • ✅ #8569 Correct children fast-path text size for cross-parent-moved elements
  • ✅ #8577 Prevent MOVE_TO_END / MOVE_TO_START crash on decorator-only elements
  • ✅ #8581 Restore Shift+Cmd+Arrow selection expansion for MOVE_TO_END / MOVE_TO_START

HTML / DOM:

  • 🆕 #8528 DOMImportExtension — composable replacement for importDOM with rule-based matchers and DOM preprocessing
  • 🆕 #8575 disabledForEditor / disabledForSession predicates on domOverride

Code:

  • 🆕 #8445 Outdent space-indented code lines on Shift+Tab
  • 🆕 #8553 Allow null Tokenizer.defaultLanguage to preserve markdown ``` round-trip
  • ✅ #8487 Detect nested <br> elements in pasted code
  • ✅ #8514 Externalize shiki dependencies in the published bundle

Clipboard:

  • 🧹 #8431 Export GetClipboardDataExtension

Extension:

  • 🆕 #8497 NormalizeInlineElementsExtension (also wired into rich/plain text)
  • 🆕 #8520 NormalizeTripleClickSelectionExtension
  • 🆕 #8549 ClickAfterLastBlockExtension
  • ✅ #8510 Set the correct default $canIndent

History:

  • 🆕 #8465 canUndo / canRedo ReadonlySignals on HistoryExtension
  • 🆕 #8537 maxDepth option for bounding the undo stack

Link / List:

  • 🆕 #8460 $setFormatFromDOM; ListItemNode imports alignment from DOM
  • 🆕 #8427 ListNode.createListItemNode factory for subclasses

Markdown:

  • ⚠️ #8503 Apply markdown shortcuts on composition-committed triggers (fixes IME-typed */_/~)
  • 🆕 #8395 $convertSelectionToMarkdownString API
  • ✅ #8402 Preserve hard line breaks in default markdown import
  • ✅ #8488 Run element-shortcut transforms on Enter
  • ✅ #8535 Fix markdown import with Unicode whitespace

React:

  • 🆕 #8489 Optional async onClose for LexicalTypeaheadMenuPlugin
  • ✅ #8411 Pass editor context to editorState.read() in markSelection
  • ✅ #8472 ExtensionComponent support for output components with required props
  • 🧹 #8485 Make onOpenEmbedModalForConfig prop optional in LexicalAutoEmbedPlugin

Rich Text / Plain Text:

  • 🆕 #8383 Opt-in escapeFormatTriggers on RichTextExtension
  • ✅ #8526 Insert paragraph on Enter for a block DecoratorNode NodeSelection

Table:

  • ✅ #8492 Attach window pointerdown handler when root element is set after register
  • ✅ #8502 Prevent crash when moving selection with arrow key outside of nested table

Yjs:

  • ✅ #8461 Handle node state being removed on non-TextNodes in collab v2
  • 🧹 #8527 Fix RenderSnapshot comment typo

Playground:

  • 🆕 #8464 New "markdown-editor split-view" example
  • ✅ #8419 #8432 Position FloatingLinkEditor correctly for multi-line / decorator-only links and near the editor's bottom edge
  • ✅ #8442 Prevent crash importing <details> with loose body content
  • ✅ #8444 Allow PagesExtension to be disabled by ActionsPlugin (HTML & markdown mode)
  • ✅ #8453 Convert selection to a code block in place
  • ✅ #8480 Prevent auto-zoom when focusing in the editor on iOS Safari
  • ✅ #8496 Set font-size for h2
  • ✅ #8521 Fix unawaited Playwright promises in e2e suite
  • ✅ #8534 EquationNode click → NodeSelection; empty-input Backspace removes
  • ✅ #8539 Normalize collapsible content children
  • ✅ #8565 Build dev playground in development mode

Website / Examples:

  • 🆕 #8570 Server-rendered "Copy page as Markdown" button
  • 🆕 #8546 "Tuning HistoryExtension for memory and long sessions" docs
  • 🆕 #8429 Clarify editorState null vs undefined and empty-state pitfall
  • 🆕 #8467 Document NodeState in $config JSON serialization
  • 🆕 #8471 TSDoc for LexicalNodeReplacement and withKlass
  • 🆕 #8476 Add missing @lexical/react plugin docs
  • 🧹 #8430 Inline toolbar SVG icons in examples via Vite asset handling
  • 🧹 #8403 Merge dev-node-state-style into examples/node-state-style
  • 🧹 #8473 Disable skipLibCheck in react-rich for lib-types regression coverage (#7093)
  • ✅ #8462 Add the missing links to the sandbox
  • ✅ #8463 #8483 Fix copyright statement to pass OSS automated checkup
  • ✅ #8498 update-team-data
  • ✅ #8509 Drop Vercel Analytics inject and unwrap chat input from flex parent
  • ✅ #8516 Drop docusaurus-plugin-internaldocs-fb preset
  • ✅ #8523 Correct links to included extensions
  • ✅ #8531 Remove embed=1 from non-iframe StackBlitz links
  • ✅ #8557 Fix crash on /docs/error page from undefined process

Infrastructure:

  • 🧹 #8554 Publish packages from their root directory
  • 🧹 #8512 Respect browserslist
  • 🧹 #8504 Use composite actions to simplify GitHub workflows
  • 🧹 #8494 Only cancel in-progress workflows on newer commits
  • 🧹 #8499 Run extended tests on new commits to approved PRs
  • 🧹 #8508 Drop FIXME #8348 Firefox ArrowDown workaround in Tables.spec.mjs
  • 🧹 #8522 Pin Windows e2e runners to windows-2022
  • 🧹 #8572 Use playwright install-deps instead of apt-get
  • 🧹 #8582 Upgrade @playwright/test to ^1.60.0
  • 🧹 #8541 Cover bold-italic markdown round trip
  • 🧹 #8400 Fix release workflow's missing rename from release.js to release.mjs
  • 🧹 #8401 #8404 #8405 #8406 #8408 #8409 #8415 #8540 Dependency vulnerability fixes (protobufjs, dompurify, postcss, astro, eslint, immutable, path-to-regexp, serialize-javascript)

What's Changed

New Contributors

Full Changelog: https://github.com/facebook/lexical/compare/v0.44.0...v0.45.0

2026-04-27 22:29:34
lexical

v0.44.0

v0.44.0 is a monthly release with a few minor breaking changes (deprecations), a notable new experimental DOMRenderExtension API for overriding DOM create/update/export, easier to use priority levels for registering commands, a pagination demonstration and convenient html import/export in the playground, better drag and drop support, automatic inline of CSS for HTML import, consistent CSP-safe style updates, and a wide range of bug fixes and other new features.

Breaking Changes

Deprecation of @lexical/offset OffsetView (#8350)

OffsetView / $createOffsetView never worked correctly, has no documentation, no tests, and no usage in the monorepo. It has been deprecated. The only function still in use, $createChildrenArray, is now exported from lexical itself — import it from lexical rather than @lexical/offset going forward.

Code block escape logic moved to CodeExtension (#8360)

The "press Enter three times to exit a CodeNode" logic has moved from CodeNode.insertNewAfter to a KEY_ENTER_COMMAND listener registered by CodeExtension. This fixes paste-with-blank-lines incorrectly escaping the code block. Using CodeNode without CodeExtension is now deprecated — a backwards-compatible shim with a dev-mode warning is in place, but the node is expected to stop working without CodeExtension in a future version.

New APIs

lexical / @lexical/html — Extensible DOM rendering and export (experimental) (#8353, #8368)

A new DOMRenderExtension (in @lexical/html) and EditorDOMRenderConfig (in lexical) parameterize $createDOM, $updateDOM, $exportDOM, $getDOMSlot, $shouldInclude, and $shouldExclude so that node rendering and export can be overridden via composable middleware. Use domOverride(nodes, config) to target one or more node classes, predicates, or '*' (wildcard), and call $next() from inside an override to delegate to the underlying implementation.

A new $decorateDOM(nextNode, prevNode, dom, editor) hook runs after a node is created/updated and after its children have been reconciled, intended for in-place attribute or DOM tweaks. Override ordering is a well-defined topological sort: wildcards highest, then predicates, then class hierarchy (subclass before superclass), with extension dependency order and registration order as tiebreakers.

Render-context APIs are also exposed: $withRenderContext, $getRenderContextValue, contextValue, contextUpdater, RenderContextRoot, RenderContextExport. New traversal helpers $generateDOMFromNodes and $generateDOMFromRoot populate a container using the configured render config. CreateEditorArgs.dom accepts a Partial<EditorDOMRenderConfig> to override rendering at editor creation. $isLexicalNode is also exported from lexical.

lexicalCOMMAND_PRIORITY_BEFORE_* priorities (#8375)

Five new command priorities — COMMAND_PRIORITY_BEFORE_EDITOR, _BEFORE_LOW, _BEFORE_NORMAL, _BEFORE_HIGH, _BEFORE_CRITICAL — register listeners that run most-recently-registered first, in front of the existing first-registered-first listeners at the same priority. Ordering is: highest priority first, then all BEFORE_* listeners (LIFO), then legacy listeners (FIFO). The new priorities are almost always what you want for new code; legacy priorities are unchanged so this is purely additive. See the new "Priorities and ordering" docs.

@lexical/code-prismCodePrismExtension & @lexical/code-shikiCodeShikiExtension (#8346)

Both packages now export proper extensions (CodePrismExtension, CodeShikiExtension) with {disabled, tokenizer} signal-based configuration that can be toggled and hot-swapped at runtime. The legacy CodeHighlighterShikiExtension is kept as a deprecated backwards-compatible shim.

@lexical/link / @lexical/react — Custom AutoLink punctuation (#8378)

AutoLinkExtension, registerAutoLink, and LexicalAutoLinkPlugin now accept an optional punctuation setting to override the default boundary characters (e.g. to allow colon-delimited matches without breaking URLs that contain colons).

@lexical/utilsremoveEmptyDestination for split-at-caret (#8384)

SplitAtPointCaretNextOptions (used by $insertNodeToNearestRootAtCaret and $splitAtPointCaretNext) gains a removeEmptyDestination option for finer control over whether an empty destination is replaced entirely.

@lexical/clipboard — Shared drop handlers (#8373)

New $handleRichTextDrop and $handlePlainTextDrop helpers implement Lexical-aware text drag-and-drop, including same-block drags, cross-TextNode and cross-block drops, DecoratorNode preservation, and cross-editor drags. registerRichText and registerPlainText now wire these by default.

Stable APIs (no longer @experimental) (#8354)

@lexical/extension (since v0.36.1, Sep 2025) and the NodeState API in lexical (since v0.26.0, Feb 2025) are now formally stable.

Deprecations

  • @lexical/offset OffsetView / $createOffsetView (use $createChildrenArray from lexical) (#8350)
  • @lexical/text $findTextIntersectionFromCharacters — broken, untested, undocumented (#8364)
  • CodeHighlighterShikiExtension / CodeHighlighterShikiConfig (use CodeShikiExtension) (#8346)
  • Using CodeNode without CodeExtension (#8360)

Highlights

Core:

  • ⚠️ #8350 Deprecate OffsetView; export $createChildrenArray from lexical
  • 🆕 #8353 #8368 EditorDOMRenderConfig, DOMRenderExtension, domOverride, $decorateDOM, render context, $generateDOMFromNodes, $generateDOMFromRoot, $isLexicalNode, CreateEditorArgs.dom
  • 🆕 #8375 COMMAND_PRIORITY_BEFORE_* priorities for last-registered-called-first ordering
  • 🧹 #8354 Remove @experimental from Extension and NodeState APIs
  • ✅ #8356 Workaround for synchronous Firefox focus edge case where deferred callbacks were silently discarded
  • ✅ #8349 Resolve Firefox arrow-key navigation skipping over collapsible content
  • ✅ #8361 Fall back to non-shifted key matching for Option+number shortcuts on macOS

Clipboard / Rich Text / Plain Text:

  • ✅ #8373 Drag-and-drop within the same block now correctly moves the dragged text; DecoratorNodes and formatting are preserved across drops; cross-editor drags work even with two Lexical versions on the same page

Code:

  • ⚠️ #8360 Move three-Enter code-block escape logic to a KEY_ENTER_COMMAND listener in CodeExtension; fixes blank-line paste escaping the code block
  • 🆕 #8346 Add CodePrismExtension (@lexical/code-prism) and CodeShikiExtension (@lexical/code-shiki)
  • 🧹 #8330 Upgrade shiki to ^4.0.2

Extension:

  • ✅ #8328 Remove JSX/react type dependency from DecoratorTextNode
  • ✅ #8377 LexicalExtensionEditorComposer no longer disposes the editor on unmount; lifetime is the caller's responsibility (fixes nested/caption editors after remount)

HTML / Table:

  • 🆕 #8326 Inline CSS from <style> tags during HTML import (fixes paste from Excel, Outlook, etc.)
  • ✅ #8313 Merge nested lists into the parent <li> during HTML export, eliminating duplicate ordered-list numbering

Link:

  • 🆕 #8378 Allow custom punctuation for AutoLink boundaries

List:

  • ✅ #8382 Ensure ListItemNode always has a ListNode parent (orphans are merged or floated to root)
  • ✅ #8390 Toggle checklist items on mobile tap (iOS Safari / Android Chrome) via pointerup
  • ✅ #8313 Merge nested lists into parent <li> on HTML export

Markdown:

  • ✅ #8332 Replace negative lookbehind assertions for Safari < 16.4 compatibility
  • ✅ #8365 Add an undo history entry for markdown shortcut transforms so undo no longer also undoes the triggering keystroke
  • ✅ #8311 Escape ordered-list pattern (\d+\. ) inside bullet/check list item exports to fix double-escape on round-trip
  • ✅ #8381 Code spans take precedence over inline formatting in shortcut transforms (CommonMark behavior)

Playground:

  • 🆕 #8322 New PagesExtension with PageNode, PageContentNode, PageSetupNode for paginated documents and print-friendly output
  • 🆕 #8323 Animations for collapsible section expand/collapse and arrow rotation
  • 🆕 #8379 #8386 "Convert to HTML" toolbar button (round-trips through $generateHtmlFromNodes / $generateNodesFromDOM); HTML view formatted with prettier
  • 🆕 #8346 Migrate eight playground plugins to extensions (PageBreak, Twitter, YouTube, Figma, TabFocus, Collapsible, SpecialText, code-highlight) and remove the dead LinkPlugin and StickyPlugin files
  • ✅ #8385 NodeContextMenuPlugin falls back to the root node when the click target has no Lexical node, instead of showing an empty menu
  • ✅ #8388 Use @floating-ui/react for FloatingLinkEditorPlugin positioning; correctly handles all viewport boundaries and Firefox focusNode.parentElement quirks
  • ✅ #8392 Show floating link editor for single-character links (right-bias adjacent-link lookup)

React:

  • ✅ #8329 Prevent error when editorRef is null in LexicalEditorRefPlugin
  • ✅ #8366 Add yjs as an optional peer dependency for Yarn PnP compatibility
  • ✅ #8377 Don't auto-dispose editor from LexicalExtensionEditorComposer
  • ✅ #8385 Root-node fallback in NodeContextMenuPlugin

Text:

  • 🧹 #8364 Add deprecation notice to $findTextIntersectionFromCharacters

Utils:

  • ✅ #8384 Clean up and test $insertNodeToNearestRootAtCaret edge cases; add removeEmptyDestination to SplitAtPointCaretNextOptions

Refactor (CSP-safe styles):

  • 🧹 #8372 Replace runtime cssText / setAttribute('style', …) writes with property-based style updates across lexical, @lexical/code-core, @lexical/list, @lexical/table, @lexical/yjs, and the playground

Website:

  • 🆕 #8316 Add more examples to the demos gallery and statically capture screenshots with Playwright (replaces the third-party screenshot service)
  • ✅ #8369 Fix tailwindcss docusaurus config regression (sync, not async)
  • ✅ #8371 Add a dedicated tsconfig.json for lexical-website so type errors are actually surfaced in CI

Infrastructure:

  • 🧹 #8341 Encourage agents to use extensions and $config in AGENTS.md
  • 🧹 #8355 Refactor CommonJS scripts to ESM modules
  • 🧹 #8357 Cache build + Playwright before running e2e tests
  • 🧹 #8324 Override yaml to >=1.10.3 to fix CVE-2026-33532
  • 🧹 #8358 Address simple-git dependency vulnerability
  • 🧹 #8380 Address follow-redirects dependency vulnerability
  • 🧹 #8334 Remove example/fixture lockfiles from git to reduce Dependabot noise
  • 🧹 #8336 #8347 Update stale dev dependencies to resolve Dependabot alerts

What's Changed

New Contributors

Full Changelog: https://github.com/facebook/lexical/compare/v0.43.0...v0.44.0

2026-04-10 00:31:06
lexical

v0.43.0

v0.43.0 is a monthly release with a minor breaking change, new features, and a number of bug fixes across selection, tables, markdown, and extensions. Notable highlights include a new useExtensionSignalValue hook, and CSS theme options for Yjs collaboration cursors.

Outside of the release artifact, we've refreshed the lexical.dev website thanks to @m-santanna (and inspired by design mockups from @ixahmedxi). Importantly this refresh includes a set of modern examples (examples/website-* and examples/agent-example) that are embedded on the front page and showcase current best practices.

Breaking Changes

Asynchronous parent editor delegation #8308

@lexical/react nested editors now use asynchronous parent editor delegation when needed to match pre-0.42.0 legacy behavior.

New APIs

@lexical/react - useExtensionSignalValue #8286

A new @lexical/react/useExtensionSignalValue module is available for reading signal values from extension output within React components

Highlights

Core:

  • ⚠️ #8308 Use asynchronous parent editor delegation when needed in nested editors
  • ✅ #8267 Exclude Android WebView from IS_SAFARI browser detection
  • ✅ #8273 Fix merging TextNodes when __state contains a different number of default values
  • ✅ #8266 Replace $insertNodes with $insertNodeIntoLeaf for consistent DecoratorNode behavior inside MarkNode

Code:

  • ✅ #8254 Remove usage of skipTransforms in CodeHighlighterPrism and CodeHighlighterShiki

Extension:

  • 🆕 #8286 Add @lexical/react/useExtensionSignalValue module for reading signals from React

Examples:

  • 🆕 #8281 Add a new agent example using a local LLM (Transformers.js / in-browser)
  • 🆕 #8258 Add additional website examples

Headless:

  • ✅ #8274 Use window.happyDOM.close() to ensure proper cleanup

Link:

  • ✅ #8309 Preserve cursor position when merging adjacent identical links

Markdown:

  • ✅ #8265 Preserve hard line breaks during import when newlines are preserved

Playground:

  • ✅ #8260 Use isExactShortcutMatch for Dvorak keyboard compatibility

Selection:

  • ✅ #8261 Make $getSelectionStyleValueForProperty direction-independent

Table:

  • ✅ #8259 Infer column header state from position during DOM import

Utils:

  • ✅ #8276 Fix $insertNodeIntoLeaf edge case; update @preact/signals-core, hermes, and flow dependencies

Yjs:

  • 🆕 #8271 Add CSS classes to collaboration cursor elements

Infrastructure:

  • 🧹 #8287 Upgrade ESLint 8 → ESLint 10 with flat configuration
  • 🧹 #8307 Upgrade Vite to ^7.3.2
  • 🧹 #8248 Update GitHub CI workflows and run with Node 24
  • 🧹 #8253 Update flow-bin to 0.306.0 and add incompatible-variance workaround
  • 🧹 #8277 #8315 Bump Flow and replace legacy Flow syntax with modern equivalents
  • 🧹 #8243 Update unit tests with extensions to use explicit resource management (using)

What's Changed

New Contributors

Full Changelog: https://github.com/facebook/lexical/compare/v0.42.0...v0.43.0

2026-03-20 04:19:17
lexical

v0.42.0

v0.42.0 is a monthly release with a huge number of fixes and features from a lot of new contributors. The most notable fixes are related to tables, links, extensions, and markdown. @lexical/eslint-plugin is now fully compatible with eslint 9+. The production builds are also now generated in an ascii-only format which may considerably reduce memory usage in some deployment environments.

Breaking Changes

Prism highlighting extracted from @lexical/code #8198

Prism highlighting now lives in @lexical/code-prism and the Prism related functionality in @lexical/code is now deprecated. Transitionally, there is a @lexical/code-core package which has the implementation of Lexical's code functionality without a Prism dependency.

Experimental hasFitNestedTables has been moved to the playground #8210

Due to its experimental nature and incomplete implementation, the in-progress hasFitNestedTables functionality has been moved to the playground

New APIs

lexical - $copyNode + LexicalNode.resetOnCopyNodeFrom & NodeState resetOnCopyNode

After $copyNode is used it now calls copiedNode.resetOnCopyNodeFrom(originalNode) which will reset every NodeState that has resetOnCopyNode: true in its configuration. Subclasses can override this method to reset other "ephemeral" state such as whether a ListNodeItem is checked or not.

Note that this does not influence serialization or copy and paste operations, it only affects calls to $copyNode.

@lexical/utils - $insertNodeIntoLeaf

The current behavior RangeSelection.insertNodes can split an inline ElementNode (e.g. a LinkNode) when inserting content, even if that content is also inline and could be a child of the given node. $insertNodeIntoLeaf is now provided to insert a node without splitting any containing nodes.

Highlights

Core:

  • 🧹 #8190 Change EditorThemeClasses from type to interface
  • ✅ #8218 Respect CSS scroll-padding in scrollIntoViewIfNeeded
  • ✅ #8220 deleteWord now behaves the same as deleteCharacter when at the edge of an ElementNode
  • 🆕 #8221 resetOnCopyNode configuration to NodeState and LexicalNode.resetOnCopyNodeFrom hook
  • 🆕 #8219 LexicalEditor RootListener and EditableListener can return unregister callbacks
  • ✅ #8222 Consecutive Linebreak insertion now preserves selection format

Code:

  • ⚠️ #8198 Extract Prism highlighting code to @lexical/code-prism and add @lexical/code-core to provide a dependency that does not include Prism. @lexical/code remains backwards compatible with Prism included, but you should migrate to using highlighting from @lexical/code-shiki or @lexical/code-prism.

Devtools:

  • ✅ #8230 Clean up strict mode useLexicalCommandsLog behavior

Extension:

  • 🆕 #8202 LexicalExtensionEditorComposer, NestedEditorExtension, and fixed SharedHistoryExtension with playground refactor

Markdown:

  • ✅ #8170 Enforce CommonMark flanking rules for trailing spaces
  • ✅ #8192 Update backslash handling to comply with CommonMark
  • ✅ #8211 Convert tabs to TabNode on import

Link:

  • ✅ #8165 Enable autolink matching when unlinked
  • 🆕 #8236 Merge adjacent LinkNodes with identical attributes

Eslint Plugin:

  • 🧹 #8227 Add flat configuration and full eslint 9+ support for @lexical/eslint-plugin

List:

  • 🆕 #8213 Create copies of ListNode/ListItemNode in split-like operations

React:

  • 🧹#8199 Remove deprecated ContextMenu, consolidate menu rendering with backward-compatible menuRenderFn

Table:

  • ✅ #8187 Use monolithic listener for SELECTION_CHANGE_COMMAND and deselection handler
  • ✅ #8193 Improve nested table selection by using monolithic pointer event handling
  • ✅ #8195 Prevent single-cell table selection after exiting table selection
  • ✅ #8200 Call $handleTableSelectionChangeCommand once instead of per-table
  • 🧹 #8215 Add test for mouse leaving browser window during table selection
  • ⚠️ #8210 hasFitNestedTables functionality moved to the playground
  • ✅ #8234 Handle table selections crossing into/out of nested tables

Utils:

  • 🆕 #8206 Add $insertNodeIntoLeaf with example usage in playground DateTimeNode

Misc:

  • 🧹 #8229 #8235 #8226 #8241 Add and fix afterCloneFrom and accessor implementations

Infrastructure

  • 🧹 #8239 Upgrade rollup packages and configure terser for ascii_only output

Playground:

  • ✅ #8186 Add fallback for dimensionless images to prevent collapse
  • 🧹 #8188 Remove legacy events mode
  • 🆕 #8183 Nested tables resize themselves with hasFitNestedTables: true
  • ✅ #8214 Use inline style for LayoutContainerNode import
  • ✅ #8228 Fix cursor position after EquationNode
  • ✅ #8224 make clear formatting work on multiple paragraphs

What's Changed

New Contributors

Full Changelog: https://github.com/facebook/lexical/compare/v0.41.0...v0.42.0

2026-02-25 12:03:20
lexical

v0.41.0

v0.41.0 is a monthly release with a huge number of fixes and features from a lot of new contributors. The most notable fixes are related to IME, non-ascii keyboard layouts, and markdown.

Breaking Changes

--lexical-indent-base-value CSS moved to root element #8132

The --lexical-indent-base-value CSS custom property is now only read from the root element of the editor, so any CSS or code to set this value must target that rather than an indented element in the document. This allows the indent to be computed while the document is being rendered without layout thrashing.

Highlights

Core

  • ⚠️ #8132 Breaking Change: Fix --lexical-indent-base-value CSS variable override
  • ✅ #8111 Prevent layout thrashing when setting element indent for no indent case
  • 🆕 #8115 Support legacy 'align' attribute in ParagraphNode importDOM
  • ✅ #8121 IME: Do not move anchor when inserting composition start char
  • ✅ #8142 IME: Fix history not recording composing character
  • ✅ #8148 IME: Do not apply format and style when moving to a different node in composition
  • ✅ #8154 IME: Fix selected text not properly deleted after IME input on Safari (macOS)
  • ✅ #8162 IME: Maintain format when multiple formatted text nodes are replaced with composition text
  • ✅ #8159 Support DOM selection in elements with slots Code
  • 🆕 #7918 Allow moving caret outside of code block
  • 🆕 #8155 Trigger keyboard shortcuts by physical key code with non-ascii keyboard layouts

Extension

  • 🆕 #8114 Implement DecoratorTextExtension applying format to DecoratorTextNode

Rich Text

  • 🆕 #8122 Support configuration of indentable nodes
  • ✅ #8152 Inherit format and style when inserting tab

Link

  • ✅ #8123 Prevent AutoLink from linking URLs inside code blocks without highlighting
  • ✅ #8127 Prevent AutoLinkNode from creating extra paragraphs
  • ✅ #8137 Add AutoLinkNode to AutoLinkExtension node dependencies
  • ✅ #8138 $toggleLink should remove the whole link when selection is collapsed
  • ✅ #8164 Prevent creation of nested LinkNode
  • ✅ #8158 Transform to move blocks outside of LinkNode List
  • 🆕 #8092 Preserve ordered list numbering when split by blocks or paragraphs
  • ✅ #8118 Remove empty parent node in nested list
  • 🆕 #8105 Add focus event option for the checklist extension

Table

  • 🆕 #8097 Implement "fit nested tables" for nested table pasting
  • 🆕 #8094 Support 'scope' attribute in HTML import for th tags
  • ✅ #8131 Preserve Background Color when pasting table rows inside table

Markdown

  • ✅ #8116 Fix nested fenced code blocks parsing and export
  • 🆕 #8140 Support Enter key after ``` to create code block
  • ✅ #8161 Prevent markdown shortcut link transformer from being too greedy

Playground

  • 🆕 #8063 Rearrange table columns
  • ✅ #8134 Increase toolbar z-index to prevent content overlap
  • ✅ #8149 Code block formatting in unintended adjacent lines

What's Changed

New Contributors

Full Changelog: https://github.com/facebook/lexical/compare/v0.40.0...v0.41.0

2026-02-02 12:53:46
lexical

v0.40.0

v0.40.0 is a monthly release primarily focusing on bug fixes and infrastructure such as the move from npm to pnpm.

Some very notable changes include:

  • Some major fixes and refactoring to markdown parsing #8093 #8085
  • Several of the most common utilities were moved from @lexical/utils to lexical (mergeRegister, addClassNames, removeClassNames) #8106
  • A cache coherency bug in RootNode.getTextContent() was fixed #8099

Highlights

Monorepo:

  • 🧹 #8035 Chore: Transition from npm to pnpm (also #8045 #8048 #8050 #8051 #8054 #8106)

Link:

  • ✅ #8070 Fix: Fix infinite transform loop in AutoLinkPlugin
  • ✅ #8078 Fix: Toggle links with nested children

List:

  • ✅ #8049 Fix: fix selection issue from list transform on linebreak
  • ✅ #8068 Fix: Treat whitespace-only list items as empty when pressing Enter

Markdown:

  • ✅ #8085 Fix: Fix incorrect format tag placement at link boundaries
  • ✅ #8093 Fix: Replace regex-based format matching

Core:

  • ✅ #8069 Fix: format removed on multi selection after replace
  • ✅ #8099 Fix: Refactor RootNode.__cachedText computation for coherency

Table:

  • ✅ #8076 Fix: Fix Ctrl+A to select all cells in table with merged cells
  • ✅ #8081 Fix: Fix inconsistent multi-cell selection in 2x2 tables
  • 🧹 #8088 Chore: Fix test for nested table pasting

Utils:

  • 🧹 #8106 Chore: Move functions mergeRegister, addClassNames, removeClassNames to lexical package

React:

  • ✅ #8062 Fix: Clear remote cursor immediately on collaborator refresh
  • ✅ #8065 Fix: Fix cursor disappearing in Firefox when dragging blocks
  • ✅ #8071 Chore: Expose onReposition prop on SelectionAlwaysOnDisplay

Playground:

  • 🆕 #8043 Feature: Color table resize handle
  • 🆕 #8042 Feature: Draggable block handle gliding effect
  • ✅ #8052 Fix: Draggable handle and dropdown CSS zoom fix
  • 🆕 #8057 Feature: New Table Hover Actions Plugin
  • 🆕 #8060 Feature: Column Sort for Basic Table
  • 🆕 #8066 Feature: Add button shows the component picker
  • ✅ #8087 Fix: Prevent code block line wrapping to keep line number

What's Changed

New Contributors

Full Changelog: https://github.com/facebook/lexical/compare/v0.39.0...v0.40.0