HTML5's Drag and Drop API provides a complete, native event sequence for implementing draggable interfaces — reorderable lists, file drop zones — without any external library, once its one critical gotcha is understood.
1Making An Element Draggable And Starting The Sequence
Setting draggable="true" on an element (note: many elements like images and links are draggable by default already; this attribute is most relevant for arbitrary elements like <div>) opts it into the browser's native drag behavior. The dragstart event fires the instant the user begins dragging, and is the standard place to call event.dataTransfer.setData(format, value), attaching whatever data identifies what's being dragged — an item's ID, a file reference, arbitrary text.
2The Mandatory preventDefault() In dragover
This is the single most common implementation mistake with the Drag and Drop API: by default, browsers disallow dropping on virtually all elements. A drop zone must explicitly listen for the dragover event (which fires continuously as a dragged item hovers over it) and call event.preventDefault() inside that handler — this call is the required signal overriding the browser's default 'no drop allowed here' behavior.
Without this call, the subsequent drop event simply never fires on that element at all, regardless of how correctly the rest of the implementation is written — a frequent, confusing source of 'drag and drop just doesn't work' bug reports.
3Completing The Transfer With drop
When the user releases the drag over a correctly-configured drop target, the drop event fires. Inside its handler, event.dataTransfer.getData(format) retrieves whatever value was set via setData() back in dragstart, threading the identifying data cleanly through the entire sequence without relying on any external, global state.
Calling event.preventDefault() in the drop handler as well is also standard practice, preventing the browser's default handling of the dropped data (like navigating to a dropped link's URL) from interfering with the custom application logic.
4Step-by-Step Breakdown
Native Drag And Drop, No Library Required. Before reaching for a drag-and-drop library, it's worth knowing HTML5 already ships a complete native Drag and Drop API — a sequence of events and a draggable attribute that together implement reorderable lists, file drop zones, and more.
draggable Enables Dragging, dragstart Begins The Sequence. Setting draggable="true" on an element makes it draggable by the user; the dragstart event fires the moment dragging begins, the standard place to set what data is being dragged via the event's dataTransfer object.
Starting A Drag Operation. What attribute makes an element draggable, and what event fires when dragging begins?
- →draggable="true" attribute, dragstart event
- →movable attribute, dragbegin event
- →No attribute needed; all elements are draggable by default
dragover Must Call preventDefault() To Allow Dropping. By default, browsers disallow dropping on most elements — a drop zone's dragover handler must explicitly call event.preventDefault() to signal 'this element accepts drops', or the subsequent drop event will never fire at all.
The dragover Requirement. What happens if a drop zone's dragover handler doesn't call preventDefault()?
- →Dropping still works normally regardless
- →The browser's default behavior disallows the drop, and the drop event never fires
- →It throws a JavaScript error immediately
drop Retrieves The Transferred Data. The drop event fires when the user releases the drag over a valid drop target, and event.dataTransfer.getData() retrieves whatever data was set during dragstart, completing the full drag-and-drop data transfer cycle.
Completing The Drag And Drop Cycle. How does the drop event handler access the data that was set in the dragstart handler?
- →It must be stored in a global JavaScript variable
- →Via event.dataTransfer.getData(), reading the value set during dragstart
- →It's automatically written to a data attribute on the drop target
Drag And Drop API Mastered. You now know how to start a drag with draggable/dragstart, the critical mandatory preventDefault() call in dragover that enables dropping, and how drop retrieves the transferred data via dataTransfer — a complete native drag-and-drop implementation with zero external libraries.
Make An Element Draggable. The native Drag and Drop API requires draggable="true" on the source element.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Native Drag And Drop Is Not Inherently Keyboard-Accessible And Needs A Supplementary Interaction Path
Mouse-driven drag interactions exclude keyboard-only and many switch-device users entirely — a fully accessible implementation needs an alternative mechanism, like buttons to move items up/down, alongside drag and drop.
SEO Implications
- 1
Native Drag And Drop Avoids The JavaScript Bundle Weight Of A Third-Party Library
For straightforward reordering or file-drop use cases, the native API avoids shipping and maintaining an external dependency, indirectly supporting smaller bundle size and better performance metrics.
Best Practices
Always Call preventDefault() In The dragover Handler For Any Valid Drop Target
It's the single required step that's easy to forget and the most common cause of a drag-and-drop implementation silently failing to accept drops.
Provide A Non-Drag-Based Alternative Interaction For Reordering Or Moving Content
Since native drag and drop excludes keyboard-only users, an accessible implementation needs a supplementary mechanism like explicit move-up/move-down buttons.
Frequent Bugs
A carefully implemented drop zone never actually receives a drop event, no matter how the drag is performed.
Verify event.preventDefault() is called inside the dragover event handler — its absence is the most common cause of this exact symptom.
Dropping a dragged link or image navigates the browser to that URL instead of triggering custom application logic.
Call event.preventDefault() in the drop handler as well, to override the browser's default drop-handling behavior.
Real-World Examples
A Complete Reorderable List Implementation
A simple drag-to-reorder list using the full native event sequence.
items.forEach(item => {
item.draggable = true;
item.addEventListener('dragstart', e => e.dataTransfer.setData('text/plain', item.id));
});
list.addEventListener('dragover', e => e.preventDefault());
list.addEventListener('drop', e => {
e.preventDefault();
const id = e.dataTransfer.getData('text/plain');
reorderList(id, e.target);
});