Drag and Drop API behavior differs in contenteditable
OS: macOS 14.0 · Device: Desktop or Laptop MacBook Pro · Browser: Chrome 120.0 · Keyboard: US
Open case →Scenario
Using the HTML drag-and-drop API inside or alongside contenteditable regions often diverges from behavior on plain elements: default actions, `contenteditable` hit-testing, and `beforeinput`/`drop` ordering differ by browser. Custom editors must reconcile native DnD with their own selection model.
Using the HTML drag-and-drop API inside or alongside contenteditable regions often diverges from behavior on plain elements: default actions, contenteditable hit-testing, and beforeinput/drop ordering differ by browser. Custom editors must reconcile native DnD with their own selection model.
The drag-and-drop model predates modern beforeinput and IME-aware editing. contenteditable introduces nested editable islands, shadow roots in some setups, and browser-specific default drag images for text.
drop default handling fights preventDefault when moving text within the same editable.Broken drag-to-reorder, lost selection after drop, and duplicated or missing fragments.
Chromium and Gecko differ on dragstart data transfer for selection text; WebKit has additional quirks on macOS with image drags.
dataTransfer.setData('text/plain', ...) explicitly when building custom drags.drop, sync selection from the drop caret before applying document mutations.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-0083-contenteditable-with-drag-drop-api | macOS 14.0 | Desktop or Laptop MacBook Pro | Chrome 120.0 | US | draft |
Open a case to see the detailed description and its dedicated playground.
OS: macOS 14.0 · Device: Desktop or Laptop MacBook Pro · Browser: Chrome 120.0 · Keyboard: US
Open case →Other scenarios that share similar tags or category.
On some engines (notably Firefox for Android / Fenix), beforeinput getTargetRanges() may describe the outer contenteditable host instead of the inner focused editor. Custom handlers that trust targetRanges alone may delete or insert in the parent surface while the user believes they are typing in a nested field.
In Chromium, programmatic DOM updates (normalization, wrapping, React reconciliation) while the user is typing can move the caret to the end of the contenteditable or to an unexpected boundary—especially when the mutation happens between keystrokes.
Dragging selected content inside a contenteditable region on Firefox can duplicate nodes or leave ghost fragments—different from Chrome's behavior.
Firefox may hide or misplace the caret when moving across contenteditable=true/false boundaries—widgets and embeds inside editors are affected.
The same DOM edited in contenteditable may serialize to different markup strings in Safari vs Chrome—attribute order, implied tags, and span wrappers for styles.
Have questions, suggestions, or want to share your experience? Join the discussion below.