dev-examples/shadow-dom/README.md
A minimal Vite + React app that demonstrates Lexical across a Shadow DOM boundary in a nested shape:
LexicalExtensionComposer
registering Rich Text, History, and Tab Indentation.ShadowRoot, nested in the
outer editor's tree via a DecoratorNode (NestedEditorNode).The nesting exercises Lexical's platform-only shadow support:
Selection.getComposedRanges
and Selection.direction,
because Selection.anchorNode / getRangeAt are retargeted to the shadow
host when the selection is inside a shadow tree.ShadowRoot.activeElement,
because document.activeElement only reports the outermost shadow host.Selection.setBaseAndExtent, and
keyboard navigation / word + line deletion use the native Selection.modify,
both of which operate on shadow-tree nodes directly.selectionchange fires under a nested layout — so the inner editor
wins attribution over its light-DOM parent.ShadowRoot.tsx attaches an open shadow root to a host
<div> with Element.attachShadow, then portals its children (including the
inner editor's contentEditable) into the shadow tree with createPortal.
React context flows across the portal, so the inner editor is built exactly as
it would be in the light DOM — only its DOM lives behind the shadow boundary.
The inner editor's CSS is injected as a <style> element inside the shadow
root, since shadow trees do not inherit the document's stylesheets.
The NestedEditorNode in App.tsx is a DecoratorNode whose
host sits in the outer editor's tree. Its decorate() returns a React subtree
that hosts the inner editor's LexicalExtensionComposer plus its shadow root —
so the inner editor lives at a real position inside the outer editor's
document.
The toolbar in Toolbar.tsx lives in the light DOM and
dispatches commands (FORMAT_TEXT_COMMAND, undo/redo) that act on the outer
editor's selection.
From the repository root:
pnpm install
pnpm -C dev-examples/shadow-dom dev
Then open the printed URL. Try:
Alt/Ctrl + Shift + arrow keys
and pressing Bold / Italic / Underline in the toolbar.Alt/Ctrl + Backspace/Delete in either
editor.Playwright tests in tests/ cover
rendering both editors, typing across the shadow boundary, formatting an outer
selection via the light-DOM toolbar, and word deletion in the inner editor.
They start the dev server automatically:
pnpm -C dev-examples/shadow-dom exec playwright install chromium
pnpm -C dev-examples/shadow-dom test