Multiple selections collision in Shadow DOM
OS: macOS 14.6 · Device: Desktop Any · Browser: All Browsers Latest (2024) · Keyboard: US QWERTY
Open case →Scenario
Analysis of the architectural friction between the single-selection document model and Web Component encapsulation.
The Web Selection API was standardized long before the widespread adoption of Shadow DOM. This architectural gap creates a “Selection Blindness” phenomenon: the global window.getSelection() model often treats a Shadow Host as an opaque block, while the internal shadowRoot.getSelection() provides a range that the rest of the application (and even some browser systems like find-in-page) cannot see.
When a user moves focus from a global text selection to a contenteditable region inside a Shadow Root, many browsers fail to correctly clear the previous highlight, leading to confusing “double-selection” UI.
/* Conflict Logic */
// 1. User selects "Hello World" in the light DOM.
// 2. User clicks inside <my-editor> (Shadow DOM).
// 3. window.getSelection() still reports "Hello World".
// 4. shadowRoot.getSelection() reports the active caret in the editor.
Calling document.execCommand() typically targets the range reported by window.getSelection(). If this selection is stuck at the shadow-host boundary, global commands (like ‘bold’ or ‘italic’) will do nothing to the text inside the editor.
selectionchange on the document often miss events happening inside Shadow Roots.shadowRoot.getSelection(), but synchronization with the light DOM selection is often laggy or inconsistent.Listen to selectionchange within the shadow root and explicitly notify your state manager.
this.shadowRoot.addEventListener('selectionchange', () => {
const selection = this.shadowRoot.getSelection();
if (selection.rangeCount > 0) {
this.dispatchEvent(new CustomEvent('editor-selection-change', {
detail: selection.getRangeAt(0),
bubbles: true,
composed: true
}));
}
});
Visual view of how this scenario connects to its concrete cases and environments. Nodes can be dragged and clicked.
Each row is a concrete case for this scenario, with a dedicated document and playground.
| Case | OS | Device | Browser | Keyboard | Status |
|---|---|---|---|---|---|
| ce-0571 | macOS 14.6 | Desktop Any | All Browsers Latest (2024) | US QWERTY | confirmed |
Open a case to see the detailed description and its dedicated playground.
OS: macOS 14.6 · Device: Desktop Any · Browser: All Browsers Latest (2024) · Keyboard: US QWERTY
Open case →Other scenarios that share similar tags or category.
The selection (window.getSelection()) in beforeinput events can differ from the selection in corresponding input events. This mismatch can occur during IME composition, text prediction, or when typing adjacent to formatted elements like links. The selection in beforeinput may include adjacent formatted text, while input selection reflects the final cursor position.
When the browser is zoomed (or content is scaled via CSS transforms), caret position and text selection in contenteditable elements can become inaccurate. Clicking at a certain position places the caret elsewhere, and selection highlights may not match the visual selection.
When contenteditable='false' elements are placed inside a contenteditable container, the cursor may disappear or become invisible in certain browsers, making it difficult for users to determine the text insertion point.
After copying selected text in a contenteditable region using Cmd+C, the selection is lost in Safari. The user must re-select the text to perform additional operations.
When a contenteditable element or its parent has the CSS contain property, selection behavior may be affected. Selection may not extend beyond the contained element, and caret movement may be restricted.
Have questions, suggestions, or want to share your experience? Join the discussion below.