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

  1. DropEffect Demo

    Drag the item to the drop zone and see dropEffect in both dragover and drop events. In Chrome 146 the values match; in older Chrome the drop event shows 'none'.

  2. Modifier Key Effects

    Hold Ctrl or Alt while dragging to change the drop operation. Shows how effectAllowed on the source combined with modifier keys negotiates the dropEffect, and how Chrome 146 makes the result readable in the drop handler.

  3. Dropeffect handoff trace

    Drag a source into move / copy / link / none targets and watch a live trace of dropEffect through dragover and drop, switching between 146 and pre-146 simulation.

  4. Kanban Board

    A three-column Kanban board where "To Do" uses move, "In Progress" uses copy, and "Archive" uses link. The event log shows the dropEffect received in the drop handler — in Chrome 146 it matches the value set in dragover.

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
  }
});

references