Phenomenon
When a contenteditable container has a block-level non-editable child (e.g. contenteditable="false") as the first or last child, the native Select All command (Ctrl+A or Cmd+A) does not select the entire content. Instead, the selection collapses in the direction away from the non-editable block, so that the non-editable block and possibly other nodes are left out of the range. This has been reproduced in Safari, Chrome, and Opera; it remains in Safari 15.5 as of 2022. The failure is critical for WYSIWYG editors that embed widgets, images, or other non-editable blocks at document boundaries.
Reproduction Steps
- Create a contenteditable div whose first child is a block with
contenteditable="false"(e.g. an embedded widget or image), followed by two or more editable paragraphs. - Place the caret inside one of the editable paragraphs.
- Press Ctrl+A (Windows/Linux) or Cmd+A (macOS).
- Observe the selection: it may cover only the editable paragraphs and exclude the first child, or collapse to a single caret position instead of a full range.
- Repeat with the non-editable block as the last child (e.g. widget at the end); Ctrl+A again fails to select the full content.
Observed Behavior
- Event sequence:
keydown(Ctrl+A / Cmd+A) → browser runs default “Select All” → selection is updated to a range that does not include the non-editable first/last child, or the range collapses. - Direction: The selection tends to collapse “away” from the non-editable block (e.g. if the block is first, the selection collapses toward the end but does not extend to include the block).
- Consistency: Reproduced in Safari 17, Safari 15.5, Chrome; Firefox may behave differently.
Expected Behavior
Per user expectation and accessibility, Select All should select every node inside the contenteditable root, including non-editable blocks. The resulting Range should span from the start of the first child to the end of the last child, so that Copy, Cut, or Replace operations affect the whole document.
Impact
- WYSIWYG: “Select All → Delete” or “Select All → Copy” does not cover the embedded widget, so the document is not fully replaced or copied.
- Accessibility: Keyboard-only users cannot reliably select the entire document for replacement or clipboard copy.
- Consistency: Differs from native
<textarea>and from user mental model.
Browser Comparison
- Safari (WebKit): Reproduced; selection collapses in the wrong direction relative to the non-editable block; still present in Safari 15.5 and 17.
- Chrome (Blink): Same failure when non-editable block is first or last child.
- Firefox (Gecko): May not exhibit the same bug; version-specific testing recommended.
Solutions
- Custom Ctrl+A / Cmd+A handler: On
keydown, detect Ctrl+A (or Cmd+A), callpreventDefault(), then create a Range withrange.selectNodeContents(editor)and set it on the Selection. This guarantees the entire root (including non-editable blocks) is selected. - Avoid bare non-editable boundaries: Where possible, avoid having the only non-editable block as the very first or last child; wrap with an editable block or use a placeholder so the engine’s select-all includes all nodes.
- Fallback after native Select All: On
selectionchangeor after a short timeout following Ctrl+A, check whether the current selection covers the full root; if not, run the programmatic select-all logic.