Drag and Drop API

Course 3 · Ch 4
Drag and Drop API
Native browser drag-and-drop — the event sequence, what dataTransfer actually carries, and where this API genuinely struggles

Dragging a file from the desktop into a browser, or reordering items in a list by dragging them — both rely on the same native HTML Drag and Drop API. It's older and has more rough edges than most APIs covered in this course, but understanding the actual event sequence makes it far less mysterious than it first appears.

Making an Element Draggable

<div draggable="true" id="item1">Drag me</div> <div id="dropzone">Drop here</div>

draggable="true" is required on the source element — most elements default to non-draggable, with images and links being the only common exceptions that are draggable automatically.

The Full Event Sequence

dragstart dragover* drop dragend
*dragover fires repeatedly, continuously, while hovering over a valid drop target — not just once
EventFires onPurpose
dragstartThe draggable source elementSet up dataTransfer with what's being dragged
dragoverEvery potential drop target, repeatedlyMust call preventDefault() or the drop is rejected entirely
dropThe actual drop targetRead dataTransfer, do the actual work
dragendThe original source elementClean up — runs whether the drop succeeded or was cancelled
Forgetting preventDefault() in dragover is the single most common reason "nothing happens" on drop
By default, the browser rejects drops on almost everything — dragover's handler must explicitly call event.preventDefault() to tell the browser "yes, this is a valid place to drop." Skipping this one line is responsible for the vast majority of "my drag and drop isn't working at all" debugging sessions.

dataTransfer — Carrying Information Between Source and Target

const source = document.getElementById('item1'); const dropzone = document.getElementById('dropzone'); source.addEventListener('dragstart', (event) => { event.dataTransfer.setData('text/plain', event.target.id); }); dropzone.addEventListener('dragover', (event) => { event.preventDefault(); // required — see warning above }); dropzone.addEventListener('drop', (event) => { event.preventDefault(); const draggedId = event.dataTransfer.getData('text/plain'); const draggedElement = document.getElementById(draggedId); event.target.appendChild(draggedElement); // actually move it in the DOM });

setData/getData work with a MIME-type-like key, allowing several formats of the same data to be set simultaneously — useful when dragging between different applications (a browser tab and a desktop file manager) that each expect a different data format.

Dragging Files From the Desktop

dropzone.addEventListener('drop', (event) => { event.preventDefault(); const files = event.dataTransfer.files; // a genuine FileList, same type as a file input for (const file of files) { console.log(file.name, file.size); } });

Dragging files from the operating system into the browser populates event.dataTransfer.files with the same FileList type the File API (next chapter) works with directly — drag-and-drop file upload and a traditional <input type="file"> ultimately produce the same kind of object to work with.

Where Native Drag and Drop Genuinely Struggles

  • Touch devices. The native API is mouse-event-based at its core and doesn't translate cleanly to touch — mobile drag interactions typically need a separate, custom-built touch-event implementation.
  • Visual feedback during the drag. Customising the default drag "ghost image," giving real-time visual feedback about valid/invalid drop zones, and smooth reordering animations all require considerable additional work beyond the bare API.
  • Accessibility. Drag-and-drop interactions are inherently difficult for keyboard-only and screen reader users — a genuinely accessible interface needs an equivalent non-drag way to accomplish the same action (e.g. "Move up"/"Move down" buttons alongside drag handles).
For anything beyond a simple file-drop zone, many real projects reach for a library
Reordering lists, Kanban boards, and similar complex drag interactions are commonly built with a dedicated library (SortableJS and similar) rather than the raw native API directly — they handle touch support, accessibility fallbacks, and animation smoothing that the bare API leaves entirely up to you. Worth knowing the native API exists and how it fundamentally works, even when a library ends up handling the actual implementation.

Chapter 4 Quick Reference

  • draggable="true" — required on the source element; images/links are draggable by default
  • Event sequence: dragstart → dragover (repeatedly) → drop → dragend
  • dragover MUST call preventDefault() — otherwise the drop is rejected by default; the most common bug
  • dataTransfer.setData/getData — carries data between source and target, keyed by a MIME-type-like string
  • event.dataTransfer.files — a real FileList when files are dragged from the desktop, same type as a file input
  • Native API struggles with: touch devices, custom drag visuals, accessibility — often handled via a dedicated library instead
  • Next chapter: File API — file inputs, reading files client-side