v146 · Web APIs · Drag and Drop
Preserving dropEffect from dragover to drop
Chrome 146 fixes dataTransfer.dropEffect propagation: the value set in the dragover handler is now correctly preserved when the drop event fires. Previously, the drop event always received dropEffect = 'none', making it impossible to read in the drop handler what operation the user had negotiated during dragover.
concepts
-
DropEffect Demo
Drag the item to the drop zone and see
dropEffectin bothdragoveranddropevents. In Chrome 146 the values match; in older Chrome thedropevent shows'none'. -
Modifier Key Effects
Hold Ctrl or Alt while dragging to change the drop operation. Shows how
effectAllowedon the source combined with modifier keys negotiates thedropEffect, and how Chrome 146 makes the result readable in thedrophandler. -
Dropeffect handoff trace
Drag a source into move / copy / link / none targets and watch a live trace of
dropEffectthroughdragoveranddrop, switching between 146 and pre-146 simulation. -
Kanban Board
A three-column Kanban board where "To Do" uses
move, "In Progress" usescopy, and "Archive" useslink. The event log shows thedropEffectreceived in thedrophandler — in Chrome 146 it matches the value set indragover.
why it shipped
The HTML Drag and Drop API lets a dragover handler set dataTransfer.dropEffect to negotiate whether a drop should copy, move, or link an item. The drop handler needs to read this value to know which operation to perform. The spec requires the value to carry over, but Chrome delivered 'none' to the drop handler — a longstanding interop bug. Chrome 146 aligns with Firefox and Safari, making the negotiated effect readable at the moment it matters.
the fix
// Draggable source
draggable.addEventListener('dragstart', event => {
event.dataTransfer.effectAllowed = 'copyMove'; // what source allows
event.dataTransfer.setData('text/plain', 'payload');
});
// Drop target
target.addEventListener('dragover', event => {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy'; // negotiate the operation
});
target.addEventListener('drop', event => {
event.preventDefault();
// Before Chrome 146: event.dataTransfer.dropEffect was always 'none'
// Chrome 146+: correctly reflects what dragover set
console.log(event.dataTransfer.dropEffect); // 'copy'
if (event.dataTransfer.dropEffect === 'move') {
removeFromSource(); // caller should remove the original
} else if (event.dataTransfer.dropEffect === 'copy') {
duplicateAtTarget(); // keep original, add copy
}
});