v0.50.0 is a monthly release with a large number of PRs to improve correctness, accessibility, and performance. It also ships HMR support, compiled keyboard shortcut dispatch, and screen reader announcements for headings and auto-links, and build-time tree-shaking annotations (in a new @lexical/compiler package). We have upgraded to pnpm 11, so if you're working directly with the monorepo you will need to upgrade as well.
Breaking Changes
lexical—LexicalNode.replace()no longer re-homes a named slot value. A slot value has no parent (its up-link is__slotHost), so replacing one through the tree API now fails with an actionable invariant instead of a generic parent error; use$setSloton the owning host.$setBlocksTypeand the block converters skip slot values for the same reason (#8938, #8905, #8901)@lexical/clipboard— The Lexical JSON clipboard payload (application/x-lexical-editor) now asksexcludeFromCopy('clone')instead of'html', matching the destination the rest of that payload already uses. Nodes that opt out of HTML export but expect to survive a clone (e.g.MarkNode) are copied again; customexcludeFromCopyimplementations that keyed on'html'for this channel should be updated (#8995)
Behavior Changes to Watch
These were merged as bug fixes rather than breaking changes, but they change observable output — check them if you snapshot HTML, assert on the DOM, or depend on the old editing behavior.
- HTML export emits a terminating
<br>. A trailingLineBreakNodein a block now exports an extra<br data-lexical-managed-linebreak="true">, mirroring what the reconciler already puts in the live DOM, so<p>a<br></p>survives an export/import round trip. Both importers drop the marker, but exported-HTML fixtures will differ (#8765) - Rich text mounts an
aria-liveregion by default.HeadingAnnounceExtension(viaRichTextExtension) andAutoLinkAnnounceExtension(viaAutoLinkExtension) depend onAriaLiveRegionExtension, which appends a live-region<div>todocument.body— or to the configuredownerelement. Opt out withconfigExtension(HeadingAnnounceExtension, {disabled: true})(#8908, #9071) - Select-all delete and cut now remove the blocks. A whole-document range is widened to the top-level blocks, so the editor is left on a fresh empty paragraph instead of an emptied heading, quote or list that went on styling the next character typed — and Cmd+X now carries those blocks, so pasting restores them (#8884)
- A formatted paragraph copies as a block.
ParagraphNode.extractWithChildnow keeps a fully-selected paragraph that carries alignment, indent or inline style in the payload as a block, instead of splicing its children in and dropping that formatting. Paragraphs with none of it serialize as before (#9059) importJSONapplies registered node replacements. JSON deserialization builds through$create, so a replacement's class is constructed and itsimportJSONruns; a deprecatedreplacewithoutwithKlassis still applied the old way (#9061, #9090)- Listener return types tightened. Only
RootListenerandEditableListenerare typed to return a cleanup function; returning one from another listener is now a TypeScript error — it was never called (#8990) - Collab and Markdown output shifts.
@lexical/yjsno longer syncs roottextFormat/textStyle(#8940); Markdown import restructures nested mixed-type lists and stores fence metadata in a newmdCodeMetanode state (#8283) - New invariants. Replacing a slot value (#8938), an
insertTextanchor that is not a text node, an emptyKeyboardShortcutMatchkey, and acreateBindingV2getXmlText/getXmlElementthat returns an un-integrated or misnamed type (#6483) now throw instead of failing later
Deprecations
lexical—CAN_UNDO_COMMANDandCAN_REDO_COMMANDare deprecated in favor of thecanUndo/canRedosignals onHistoryExtension. A command only reports a change, so a listener registered after initialization can't read the current value; the signal always holds it (#8918)
New APIs & Features
@lexical/compiler— New package that injects/* @__PURE__ */annotations and inlines the trivial side-effect-free factories (defineExtension,configExtension,safeCast,createCommand,createState, …) at build time, as a Rollup and Vite plugin. Hand-written annotations on those calls are no longer needed (#9086)@lexical/extension—HMRExtension(withHMRConfig,HMROutput,HotContext) keeps an editor alive across hot module replacement.@lexical/reactgainsLexicalMenuOption,LexicalAutoEmbedPluginUtils,LexicalTypeaheadMenuPluginUtilsandLexicalCollaborationContextUtilsentry points that split values out of component modules so Fast Refresh works — the original exports are unchanged (#8959)lexical/@lexical/extension— Compiled keyboard shortcut dispatch:compileKeyboardShortcuts,CONTROL_OR_ALT,CONTROL_OR_META,CONTROL_OR_OTHER_KEY,keyboardEventMaskForPlatformand theKeyboardShortcut*types in core, plusKeyboardShortcutsExtensionandformatKeyboardShortcutfor declaring named shortcuts and rendering them per platform (#8876)@lexical/rich-text—HeadingAnnounceExtensionannounces heading changes to screen readers, on by default inRichTextExtension;@lexical/link'sAutoLinkAnnounceExtensiondoes the same when typing turns text into a link, on by default withAutoLinkExtension(#8908, #9071)@lexical/list— Opt-inWordListImportExtensionreconstructs list structure from Microsoft Word HTML (MsoListParagraphclasses andmso-listmetadata) on paste (#9082)@lexical/rich-text— NewshouldHandlePasteAsFilesconfig (and exporteddefaultShouldHandlePasteAsFiles, plus an optional third argument toregisterRichText) lets an app decide that pasted files win over the incidentaltext/htmlbrowsers attach when an image is copied. The default is unchanged: files are handled only when the clipboard carries no text content (#8902, #8699)@lexical/yjs—CreateBindingV2Options__EXPERIMENTALallows a custom YjsXmlText, so a Lexical document can live inside a larger shared type (#6483)lexical— New$getSelectionSlotFrame(#9052) and$flushSyncAfterUpdate(#9067) exports;$isBlockFullySelectedmoved into core and is still re-exported from@lexical/utils(#9059);@lexical/tablenow exports theTableCellHeaderStatetype (#9038); JSDoc added across the core exports (#8895)- Performance — Bulk inserts and removals no longer walk the parent's children to compute an offset the selection never reads, which made those operations quadratic (#9108)
Notable Fixes
Named slots & the node tree
- Line breaks inside a slot insert and delete correctly (#8899); a managed line break is restored in wrapped slot hosts (#9066); reading the parent style of a slot value no longer throws (#8950); a slot-bearing decorator host is deleted correctly at the selection boundary (#8905)
replace()keeps the parent's__sizecorrect when handed a sibling (#8917), andspliceno longer creates a sibling cycle when it re-inserts a node after the range (#8916)
Node state, copy/paste & serialization
- HTML import/export and slot frames carry the whole node state, keeping the HTML and Lexical clipboard payloads in agreement (#9052); copy and insert keep the original node's state (#9059); list operations and DOM import/export keep the node's own state (#9050); inline node transforms keep the node and its DOM in step (#9060); code blocks keep the caret and their own attributes (#9056); blocks pasted after consecutive line breaks are preserved (#8890)
- Nodes keep their own properties across clone, split and export:
EditorState.clone()keeps_parsed(#8989),AutoLinkNode.insertNewAfterkeeps its properties (#8996), a split heading keeps its format and style (#8926), split lists continue numbering from the item's value (#8913), and in the playground the Excalidraw drawing and size survive a clone (#8984), a closed collapsible no longer exports as open (#8994), mention nodes keep their display text (#8999), custom nodes keep the theme classescreateDOMapplied (#9049) and an unparseabledata-lexical-datetimeno longer breaks serialization (#9047)
Selection & editing
- Selection and caret state are correct at block and shadow root boundaries (#9058); the block cursor shows before block decorators regardless of the preceding sibling (#8888); the select-all highlight stays visible next to boundary decorators (#8931);
$extendCaretToRange's focus is a real, flippable position (#8930); Shift+Arrow extends adjacent node selections (#9067); clicking inside a selected node no longer deselects it (#8910) insertNodesno longer leaves an empty paragraph after a block inserted at the end of a paragraph (#9099);insertTextno longer throws when the caret is inside a segmentedTextNode(#9104); Backspace works at a soft line-wrap boundary in Firefox (#9101); Enter on iOS no longer moves the caret past the acceptance boundary (#8941); iPadOS desktop-class user agents are detected (#8909); dropped files are forwarded when the drop point has no caret (#9076); clear-formatting resets the cached selection format (#8889)
Tables & lists
- Table grid indices, cell state and boundary selection fixed (#9054); alignment covers every cell a table selection touches (#9043); inserting a column steps by the visited cell's
colSpan(#8911);importDOMimports thedirattribute (#8914);toggleHeaderStylestays insideTableCellHeaderStates(#9038); thetrackTableMutationObserveris disconnected inremoveListeners(#9074); text cursor placement is reliable when tapping cells on touch devices (#8827) - The stale depth theme class is removed when a list changes depth (#9077); rapid clicks on the same checklist checkbox are no longer throttled (#9102); focus stays in the editor when a press lands on a check list item's label (#9088)
Links, Markdown & HTML
TOGGLE_LINK_COMMAND's object payload no longer discards the configured attributes (#8985);$toggleLinkapplies thetitleattribute for aNodeSelection(#8993);LinkNode/AutoLinkNodegetters read the latest state (#9080); middle-clicked links open in a new tab (#9078); the auto embed menu only opens when a bare link is pasted (#9091); the playground'svalidateUrlno longer accepts a sentence that merely contains a URL (#9097)- Markdown round trips keep fenced code and enclosing blocks intact (#9055); nested mixed-type lists import with the right structure (#8283); a URL containing parentheses imports correctly (#9093); no dangling closing tag when a selection slices a format to whitespace (#8961); no stray leading newline when a selection starts below the first block (#8960)
- A trailing line break survives HTML export and re-import (#8765);
exportDOMreturning aDocumentFragmentapplies itsafter()replacement (#8997);parseSelectorrejects an empty selector group (#9083); Prism language maps are read as own properties (#8981); a function-valued style patch is applied once to an empty element (#8912);objectKlassEqualsno longer throws onundefinedor prototype-less objects (#9079)
React, collaboration & history
- Decorator portals are recreated when the editor root remounts (#8921);
Placeholderuses the SSR-safeuseLayoutEffect(#8906); the typeahead query updates during IME composition (#8942); plugins and hooks re-derive when their inputs change (#9051); focus is restored after dragging decorator blocks (#9064); multiple clicks are normalized (#8928) - Collab sync gaps between the editor state and the Yjs doc are closed, and bootstrapping the initial state no longer becomes an undo entry (#9057); only one client may bootstrap a nested editor's document, so an image caption or sticky note no longer gains a spurious leading paragraph —
shouldBootstrapis documented as at-most-one-client (#9107);SharedHistoryExtensionforwards the parentmaxDepth(#9044) and shared history stacks record the state of the editor that actually changed, alongside fixes to overlays, hit targets and shared state at the editor boundary (#9053)
Docs & tooling
- New Decorators concept guide (#8696), a React MathType example (#8692), a clarified vanilla quick start (#8801), documented
DOMImportExtensionrule evaluation order (#9089),SKIP_DOM_SELECTION_TAGbehavior during the initial editor state (#8636) and$reverseDfs(#8962), plus an OpenGraph social preview for the website (#8935) - Upgraded to pnpm 11 (#8816), cached Playwright's apt packages in CI (#9085), extracted the internal-registry OIDC exchange into a reusable action (#9084), fixed a
qsdependency vulnerability (#9072), quieted stderr noise intest-unit(#9098) and moved the playground ontoownerDocument/$getDocument()for shadow DOM safety (#8878)
What's Changed
- v0.49.0 by @etrepum in #8893
- [lexical-playground] Chore: Use ownerDocument and $getDocument() for shadow DOM safety by @mayrang in #8878
- [lexical] Documentation Update: Add JSDoc comments to core package exports by @mayrang in #8895
- [lexical][lexical-rich-text] Bug Fix: Show block cursor before block decorators regardless of preceding sibling by @ochevallier in #8888
- [lexical] Bug Fix: Line breaks in a slot can't be inserted or deleted correctly by @sahiee-dev in #8899
- [lexical-playground] Bug Fix: Reset the cached selection format on clear formatting by @Yjason-K in #8889
- [lexical-selection] Bug Fix: Handle bare slot values in $setBlocksType by @mayrang in #8901
- [lexical-react] Bug Fix: Use the SSR-safe useLayoutEffect in Placeholder by @arcs- in #8906
- [lexical][lexical-selection][lexical-list] Bug Fix: Skip named-slot values in block converters, diagnose replace() on a slot value, and delete slot-bearing decorator hosts at the selection boundary by @etrepum in #8905
- [lexical-extension] Bug Fix: Normalize multiple clicks by @levensta in #8928
- [lexical-rich-text] Feat: add pasteFileContentCheck config to let files win over incidental HTML on paste by @Sa-Te in #8902
- [lexical] Bug Fix: Detect iPadOS desktop-class user agents by @corinthionia in #8909
- [lexical-rich-text] Bug Fix: Don't deselect a selected node when clicking inside it by @alvorithm in #8910
- [lexical-table] Bug Fix: step by the visited cell's colSpan when walking left to insert a column by @LeSingh1 in #8911
- [lexical-selection] Bug Fix: apply a function-valued style patch only once to an empty element by @LeSingh1 in #8912
- [lexical-table] Bug Fix: Import the dir attribute in table importDOM by @LeSingh1 in #8914
- [lexical-react] Bug Fix: Recreate decorator portals when the editor root remounts by @alphanull in #8921
- [lexical] Bug Fix: keep parent __size correct when replace() is passed a sibling by @LeSingh1 in #8917
- [lexical] Bug Fix: don't create a sibling cycle when splice re-inserts the node after the range by @LeSingh1 in #8916
- [lexical-rich-text] Bug Fix: preserve heading format and style when splitting a heading by @LeSingh1 in #8926
- [lexical-list] Bug Fix: continue split-list numbering from the item's value, not its index by @LeSingh1 in #8913
- [lexical] Bug Fix: make $extendCaretToRange's focus a real, flippable position by @Sa-Te in #8930
- [lexical] Deprecate CAN_UNDO_COMMAND and CAN_REDO_COMMAND in favour of HistoryExtension's signals by @LeSingh1 in #8918
- [lexical-website] Add opengraph social preview image by @alex-js-ltd in #8935
- [Breaking Change][lexical][lexical-utils][lexical-selection][lexical-website] Bug Fix: LexicalNode.replace no longer re-homes named slots by @etrepum in #8938
- [lexical-playground] Chore: Stabilize the collab dangling-text e2e tests by @etrepum in #8933
- [scripts] Bug Fix: wait out minimumReleaseAge boundary in example installs by @etrepum in #8934
- [lexical-yjs] Bug Fix: Don't sync root textFormat/textStyle by @patrick-atticus in #8940
- [lexical-react] Bug Fix: Update the typeahead query during IME composition by @hamo-o in #8942
- [lexical-selection] Bug Fix: don't throw reading the parent style of a slot value by @LeSingh1 in #8950
- [lexical-utils] Documentation Update: describe $reverseDfs instead of $reverseDfsIterator by @luantaraschi in #8962
- [lexical][lexical-website] Documentation Update: SKIP_DOM_SELECTION_TAG is ignored during initial editor state by @spokodev in #8636
- [lexical] Bug Fix: keep the select-all highlight visible next to boundary decorators by @etrepum in #8931
- [lexical-markdown] Bug Fix: don't emit a dangling closing tag when a selection slices a format to whitespace by @luantaraschi in #8961
- [lexical-code-prism] Bug Fix: read the language maps as own properties by @luantaraschi in #8981
- [lexical] Bug Fix: Don't move the caret past the acceptance boundary for Enter on iOS by @SujinKim1127 in #8941
- [lexical-playground] Bug Fix: keep the Excalidraw drawing and its size across a node clone by @LeSingh1 in #8984
- [lexical-link] Bug Fix: TOGGLE_LINK_COMMAND object payload no longer discards the configured attributes by @LeSingh1 in #8985
- [lexical] Bug Fix: EditorState.clone() keeps the _parsed flag by @LeSingh1 in #8989
- [lexical] Bug Fix: only root and editable listeners may return a cleanup function by @LeSingh1 in #8990
- [lexical-link] Bug Fix: $toggleLink applies the title attribute for a NodeSelection by @LeSingh1 in #8993
- [lexical-playground] Bug Fix: $createMentionNode keeps the display text it is given by @LeSingh1 in #8999
- [lexical-playground] Bug Fix: a closed collapsible container no longer exports as open by @LeSingh1 in #8994
- [Breaking Change][lexical-clipboard] Bug Fix: the Lexical clipboard payload asks excludeFromCopy for the clone destination by @LeSingh1 in #8995
- [lexical-link] Bug Fix: AutoLinkNode.insertNewAfter keeps the node's own properties by @LeSingh1 in #8996
- [lexical-html] Bug Fix: apply the after() replacement when exportDOM returns a DocumentFragment by @LeSingh1 in #8997
- [lexical-table] Bug Fix: align every cell a table selection covers, not just the naive rect by @LeSingh1 in #9043
- [lexical-playground] Bug Fix: importing an unparseable data-lexical-datetime no longer breaks serialization by @LeSingh1 in #9047
- [lexical-history] Bug Fix: SharedHistoryExtension forwards the parent maxDepth by @LeSingh1 in #9044
- [lexical-table] Bug Fix: toggleHeaderStyle stays inside TableCellHeaderStates by @LeSingh1 in #9038
- [lexical] Bug fix: apply nodes replacement for importJSON by @fantactuka in #9061
- [lexical-text][lexical-mark][lexical-link][lexical-playground] Bug Fix: inline node transforms keep the node and its DOM in step by @LeSingh1 in #9060
- [lexical-rich-text][lexical-a11y] Feature: Announce heading changes by default in rich text by @Electro-Jam in #8908
- [lexical-react][lexical-playground] Bug Fix: restore focus after dragging decorator blocks by @luantaraschi in #9064
- [lexical][lexical-extension][lexical-playground] Refactor: Compiled keyboard shortcut dispatch by @mayrang in #8876
- [lexical] Bug Fix: restore managed line break in wrapped slot hosts by @luantaraschi in #9066
- build(deps): bump mermaid from 11.15.0 to 11.16.1 by @dependabot[bot] in #8980
- [lexical-rich-text] Bug Fix: extend adjacent node selections with Shift+Arrow by @luantaraschi in #9067
- [lexical] Chore: Fix qs package dependency vulnerability by @vijayojha89 in #9072
- [lexical-website] Documentation Update: Add Decorators concept guide by @WilliamK112 in #8696
- [lexical-markdown] Bug Fix: don't prefix a newline when a selection starts below the first block by @luantaraschi in #8960
- [lexical-table] Bug Fix: disconnect trackTable MutationObserver in removeListeners by @kaminski-dev in #9074
- [lexical-utils] Bug Fix: objectKlassEquals throws on undefined and prototype-less objects by @nithin42 in #9079
- [lexical-html] Bug Fix: reject an empty selector group in parseSelector by @luantaraschi in #9083
- [lexical-list] Feature: opt-in Word list paste overlay by @smrifat1411 in #9082
- [lexical-link] Bug Fix: read the latest state in LinkNode and AutoLinkNode getters by @nithin42 in #9080
- [lexical-list] Bug Fix: remove the stale depth theme class when a list changes depth by @nithin42 in #9077
- [ci] Extract the internal-registry OIDC exchange into a reusable auth action by @zurfyx in #9084
- [lexical-link] Feature: Tell screen reader users when typing turns text into a link by @Electro-Jam in #9071
- [ci] Chore: cache Playwright's apt packages and skip WebKit deps on Linux by @etrepum in #9085
- docs: clarify vanilla quick start path by @alexespinoza28 in #8801
- [lexical-html][lexical-rich-text][lexical-website] Docs: clarify DOMImportExtension rule evaluation order by @etrepum in #9089
- [lexical] Feature: build the node with $create when importing JSON by @etrepum in #9090
- [lexical-code-core][lexical-code-prism][lexical-code-shiki] Bug Fix: code blocks keep the caret and their own attributes by @LeSingh1 in #9056
- [lexical-compiler][lexical-eslint-plugin-internal] Feature: inject /* @PURE */ annotations at build time by @etrepum in #9086
- [lexical-playground] Bug Fix: validateUrl no longer accepts a sentence that merely contains a URL by @Om-singhaI in #9097
- [lexical-markdown] Bug Fix: import a link whose URL contains parentheses by @luantaraschi in #9093
- [tests] Chore: silence unnecessary stderr warnings in test-unit by @etrepum in #9098
- [lexical] Bug Fix: Backspace at a soft line-wrap boundary in Firefox by @etrepum in #9101
- [lexical] Bug Fix: insertNodes no longer leaves an empty paragraph after a block inserted at the end of a paragraph by @Om-singhaI in #9099
- [lexical-list] Bug Fix: stop throttling rapid mouse clicks on the same checklist checkbox by @noeschmidt in #9102
- [lexical-react] Bug Fix: Only open the auto embed menu when a bare link is pasted by @Om-singhaI in #9091
- [lexical-history][lexical-utils][lexical-playground] Bug Fix: overlays, hit targets and shared state at the editor boundary by @LeSingh1 in #9053
- [lexical-playground] Bug Fix: custom nodes keep the theme classes createDOM applied by @LeSingh1 in #9049
- [lexical] Bug Fix: insertText no longer throws when the caret is inside a segmented TextNode by @Om-singhaI in #9104
- [lexical][lexical-rich-text][lexical-utils] Bug Fix: copy and insert keep the original node's state by @LeSingh1 in #9059
- [lexical-list] Bug Fix: list operations and DOM import/export keep the node's own state by @LeSingh1 in #9050
- [lexical-rich-text] Bug Fix: forward dropped files when the drop point has no caret by @nithin42 in #9076
- [lexical-list][lexical-playground] Bug Fix: keep the focus on the editor when a press lands in a check list item's label by @luantaraschi in #9088
- [lexical-utils] Bug Fix: Prioritize file transfer over text/html when pasting browser-copied images by @VivekJariwala50 in #8699
- [lexical][lexical-html][lexical-clipboard] Bug Fix: HTML import/export and slot frames carry the whole state by @LeSingh1 in #9052
- [lexical-yjs][lexical-react] Bug Fix: collab sync gaps between the editor state and the yjs doc by @LeSingh1 in #9057
- [lexical-link] Bug Fix: open middle-clicked links in a new tab by @nithin42 in #9078
- [lexical-table][lexical-playground][lexical-rich-text] Bug Fix: table grid indices, cell state and boundary selection by @LeSingh1 in #9054
- [lexical][lexical-table] Bug Fix: selection and caret state at block and shadow-root boundaries by @LeSingh1 in #9058
- [lexical-react][lexical-playground] Bug Fix: plugins and hooks re-derive when their inputs change by @LeSingh1 in #9051
- [lexical-markdown][lexical-mdast] Bug Fix: markdown round trip keeps fenced code and enclosing blocks intact by @LeSingh1 in #9055
- Add React MathType example by @culpen90 in #8692
- [lexical-table] Bug Fix: Fix unreliable text cursor placement when tapping table cells on touch devices by @Arman-Luthra in #8827
- [lexical-yjs][lexical-react] Feature: Allow custom Yjs XmlText by @flaviouk in #6483
- Upgrade to pnpm 11 by @zurfyx in #8816
New Contributors
- @Yjason-K made their first contribution in #8889
- @arcs- made their first contribution in #8906
- @corinthionia made their first contribution in #8909
- @alvorithm made their first contribution in #8910
- @alphanull made their first contribution in #8921
- @alex-js-ltd made their first contribution in #8935
- @luantaraschi made their first contribution in #8962
- @spokodev made their first contribution in #8636
- @SujinKim1127 made their first contribution in #8941
- @Electro-Jam made their first contribution in #8908
- @WilliamK112 made their first contribution in #8696
- @kaminski-dev made their first contribution in #9074
- @nithin42 made their first contribution in #9079
- @smrifat1411 made their first contribution in #9082
- @alexespinoza28 made their first contribution in #8801
- @Om-singhaI made their first contribution in #9097
- @noeschmidt made their first contribution in #9102
- @VivekJariwala50 made their first contribution in #8699
- @culpen90 made their first contribution in #8692
- @Arman-Luthra made their first contribution in #8827
- @flaviouk made their first contribution in #6483
Full Changelog: v0.49.0...v0.50.0