Skip to main content

Shadow DOM and iframes

A Lexical editor works when its contentEditable lives inside an open DOM ShadowRoot (for example inside a web component) or inside an <iframe> document. Both are supported out of the box — you only have to point the editor at the right root element.

Embedding in a shadow root

Attach an open shadow root, render a contentEditable inside it, and pass that element to editor.setRootElement (or, with @lexical/react, portal the ContentEditable into the shadow root — React context flows across the portal, so the editor is built exactly as it would be in the light DOM):

const host = document.querySelector('#editor-host');
const shadow = host.attachShadow({mode: 'open'});
const contentEditable = document.createElement('div');
contentEditable.contentEditable = 'true';
shadow.appendChild(contentEditable);

editor.setRootElement(contentEditable);

Lexical detects the enclosing shadow root from the root element and resolves selection through it. There is nothing else to configure.

Why it needs platform support

Inside a shadow tree the browser retargets the document's selection to the shadow host: Selection.anchorNode/focusNode and Selection.getRangeAt report the host element, not the node the caret is actually in, and document.activeElement reports the host rather than the focused element. Lexical works around this using only standard platform APIs:

Requirements and fallback

  • Open shadow roots only. A closed shadow root ({mode: 'closed'}) hides its contents from these APIs and is not supported.
  • Selection.getComposedRanges is required, which is available in recent versions of Chrome/Edge, Safari, and Firefox — see the browser compatibility table. On older engines the editor still works in the light DOM and degrades gracefully (it falls back to the standard, light‑DOM‑correct reads) rather than breaking.

Styling

Shadow trees do not inherit the document's stylesheets, so your editor/theme CSS must be placed inside the shadow root (e.g. a <style> element appended to the shadow root, an adopted stylesheet, or cloned <link>/<style> nodes). This is a property of Shadow DOM, not of Lexical.

Embedding in an iframe

An editor whose root element belongs to an <iframe> document is also supported. Lexical reads the editor's window/document from the root element (rootElement.ownerDocument.defaultView), so selection and focus are resolved against the iframe rather than the top‑level document:

const iframeDoc = iframe.contentDocument;
const contentEditable = iframeDoc.querySelector('#editor');

// createEditor / setRootElement can run in the parent frame; the editor uses
// the iframe's own window and document for selection and focus.
editor.setRootElement(contentEditable);

Selection inside an iframe is not retargeted (an iframe is a separate document, not a shadow boundary), so getComposedRanges is not involved here — the iframe's own selection already reports the correct nodes.

Reading the DOM selection in your own code

If a plugin reads the DOM selection or the focused element directly, use the shadow/iframe‑aware helpers exported from lexical instead of Selection.anchorNode / document.activeElement, so it keeps working in these contexts (all are no‑ops in the plain light DOM):

Instead ofUse
selection.anchorNode / offsetsgetDOMSelectionPoints(selection, rootElement)
selection.getRangeAt(0)getDOMSelectionRange(selection, rootElement)
document.activeElement === elgetActiveElement(el) === el
deepest focused elementgetActiveElementDeep(document)

Examples

Runnable examples live in the repository: