HTML APIs
HTML Drag and Drop
The HTML5 Drag and Drop API lets users grab an element with the mouse and drop it onto another element, powering reorderable lists, kanban boards and file-upload drop zones - all with native browser events.
What is the Drag and Drop API and when to use it?
Drag and Drop (DnD) is a native HTML5 feature that makes elements movable and defines the drop targets that can accept them. Instead of manually tracking mouse coordinates, you mark an element as draggable and respond to a set of standard drag events - the browser handles the ghost image and the visual dragging for you.
Reach for it when you want users to reorder to-do items, move cards between columns of a task board, drag files from the desktop into a custom upload zone, or rearrange a gallery. It is a desktop-first interaction - see the touch note below.
Syntax
Add draggable="true" to make an element draggable. To accept a drop, a target must cancel the browser default in its dragover handler with event.preventDefault() - otherwise the drop event never fires. Data travels between source and target through the dataTransfer object.
<div draggable="true" id="source">Drag me</div>
<div id="target">Drop zone</div>
<script>
source.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", "payload");
});
target.addEventListener("dragover", (e) => e.preventDefault()); // allow drop
target.addEventListener("drop", (e) => {
e.preventDefault();
const data = e.dataTransfer.getData("text/plain");
});
</script>The core drag events
- dragstart - fires on the source when the drag begins; store data in dataTransfer here.
- drag - fires repeatedly on the source while it is being dragged.
- dragenter - fires on a target when a dragged item enters it.
- dragover - fires continuously over a target; call event.preventDefault() to allow a drop.
- dragleave - fires when the dragged item leaves a target.
- drop - fires on the target when the item is released; read dataTransfer and call preventDefault().
- dragend - fires on the source when the drag finishes, whether it was dropped or cancelled.
Example: a fully working drag demo
Paste this complete page into the editor and drag the coloured box into the dashed zone. The zone highlights while you drag over it and updates its text on drop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Drag and Drop demo</title>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; }
#item { padding: 12px; background: #4f46e5; color: #fff;
width: 130px; text-align: center; cursor: grab; border-radius: 6px; }
#zone { margin-top: 18px; padding: 34px; border: 2px dashed #999;
border-radius: 8px; text-align: center; color: #555; }
#zone.over { border-color: #4f46e5; background: #eef2ff; color: #4f46e5; }
</style>
</head>
<body>
<h3>Drag the box into the zone</h3>
<div id="item" draggable="true">Drag me</div>
<div id="zone">Drop here</div>
<script>
const item = document.getElementById("item");
const zone = document.getElementById("zone");
item.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", "Delivered!");
e.dataTransfer.effectAllowed = "move";
});
zone.addEventListener("dragover", (e) => {
e.preventDefault(); // REQUIRED to allow a drop
e.dataTransfer.dropEffect = "move";
zone.classList.add("over");
});
zone.addEventListener("dragleave", () => zone.classList.remove("over"));
zone.addEventListener("drop", (e) => {
e.preventDefault();
const text = e.dataTransfer.getData("text/plain");
zone.textContent = text;
zone.classList.remove("over");
});
</script>
</body>
</html>More examples: the dataTransfer object
Every drag event carries a dataTransfer object that holds what is being dragged. Use setData(format, value) in dragstart and getData(format) in drop to move information between source and target. For files dragged from the operating system, read the dataTransfer.files list instead.
dropZone.addEventListener("dragover", (e) => e.preventDefault());
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
const files = e.dataTransfer.files; // a FileList of dropped files
for (const file of files) {
console.log(file.name, file.type, file.size + " bytes");
}
});dataTransfer methods and properties
| Member | Purpose |
|---|---|
| setData(format, data) | Store dragged data under a MIME type such as 'text/plain' |
| getData(format) | Retrieve stored data during the drop event |
| clearData(format?) | Remove one or all stored data entries |
| files | A FileList of files dragged from the operating system |
| effectAllowed | Which operations are allowed: copy, move, link, etc. |
| dropEffect | The operation shown by the cursor during dragover |
You must call event.preventDefault() in BOTH the dragover and the drop handlers. Without it in dragover, the browser never fires the drop event and the item simply snaps back - this is the single most common drag-and-drop bug.
Native HTML5 Drag and Drop works well on desktop but has poor support on touch devices. For mobile-first reordering, use Pointer Events or a library such as SortableJS or dnd-kit that emulates the behaviour with touch support.
Key Takeaways
- Set draggable="true" to make any element draggable.
- Store data in dragstart with dataTransfer.setData() and read it in drop with getData().
- Call event.preventDefault() in dragover AND drop, or the drop will never happen.
- Use dataTransfer.files to accept files dragged in from the desktop.
- For touch devices, prefer a pointer-based library since native DnD barely works on mobile.
