Scenario

Firefox Drag and Drop failures in contenteditable

Analysis of Firefox's inconsistent handling of native Drag-and-Drop operations within editable containers.

drag-drop
Scenario ID
scenario-firefox-drag-drop-issues

Details

Problem Overview

Drag-and-Drop (DnD) within contenteditable is a complex interaction involving the DataTransfer API and internal DOM mutations. While Chromium and WebKit have converged on a “move” behavior that dispatches specific beforeinput types (like deleteByDrag), Firefox often fails to execute the default action. This leaves the text at the source and provides no automated way to teleport it to the destination.

Observed Behavior

Scenario 1: Intra-Editor Move Failure

In recent versions (v130+), selecting a text fragment and dragging it within the same editor results in a visual ghost but no actual DOM mutation upon drop.

/* Observer Sequence */
element.addEventListener('dragstart', (e) => {
    console.log('1. Drag started'); // Fires
});
element.addEventListener('drop', (e) => {
    console.log('2. Drop fired'); // Fires, but default action is skipped by Engine
});

Scenario 2: Nested Structure Corruption

Dragging content into or out of nested <span> elements often causes Firefox to generate redundant wrapper nodes, breaking the logical tree of the editor.

Impact

  • Broken User Intuition: Modern web users expect editors to behave like native word processors (Word, Pages).
  • History Divergence: If a framework manually handles the drop but the browser (eventually) performs a partial move, the undo history becomes corrupted.

Browser Comparison

  • Gecko (Firefox): Significant inconsistency. Requires manual implementation of the “move” logic via DataTransfer.
  • Blink (Chrome/Edge): Highly reliable. Handles beforeinput (deleteByDrag/insertFromDrop) natively.
  • WebKit (Safari): Reliable on Desktop; limited support for complex DnD on iOS.

Solutions

1. The “Manual Teleport” Strategy

Explicitly set and get the plain text/HTML in the data transfer object to ensure cross-platform compatibility.

element.addEventListener('dragstart', (e) => {
    const range = window.getSelection().getRangeAt(0);
    e.dataTransfer.setData('text/plain', range.toString());
    e.dataTransfer.effectAllowed = 'move';
});

element.addEventListener('drop', (e) => {
    e.preventDefault();
    const data = e.dataTransfer.getData('text/plain');
    const targetRange = document.caretRangeFromPoint(e.clientX, e.clientY);
    
    // Explicitly delete source and insert at target
    // Warning: Requires custom transaction handling in frameworks like Lexical
    moveFragment(sourceRange, targetRange, data);
});

References

Scenario flow

Visual view of how this scenario connects to its concrete cases and environments. Nodes can be dragged and clicked.

React Flow mini map

Variants

Each row is a concrete case for this scenario, with a dedicated document and playground.

Case OS Device Browser Keyboard Status
ce-0310-firefox-nested-span-drag-en Any Any Desktop Any Firefox 90+ Any draft
ce-0554-firefox-drag-drop-textarea-en Any Any Desktop Any Firefox 90+ Any draft
ce-0569 Linux Ubuntu 24.04 Desktop Any Firefox 132.0 US QWERTY confirmed

Browser compatibility

This matrix shows which browser and OS combinations have documented cases for this scenario. Click on a cell to view the specific case.

Confirmed
Draft
No case documented

Cases

This scenario affects multiple languages. Cases are grouped by language/input method below.

Any

2 cases

US QWERTY

1 case

Related Scenarios

Other scenarios that share similar tags or category.

Tags: firefox

Text caret is invisible on position:relative elements

When editing content inside an element with `position:relative`, the text caret (cursor) is completely invisible. Text can be typed and appears in the editor, but there's no visual feedback of where the insertion point is located.

2 cases
Tags: firefox

contenteditable inheritance behavior is inconsistent

When a parent element has contenteditable="true" and a child element has contenteditable="false", the inheritance behavior is inconsistent across browsers. Some browsers allow editing in the child, while others correctly prevent it. The behavior may also differ when the child has contenteditable="inherit" or no contenteditable attribute.

1 case

Comments & Discussion

Have questions, suggestions, or want to share your experience? Join the discussion below.