Drag and Drop API
The native HTML5 Drag and Drop API lets users pick up an element with the mouse and drop it somewhere else — reordering a list, moving a card between columns, or dropping a file onto the page. It works through a small set of attributes and events built into the browser, no library required.
Making an Element Draggable
Add the draggable attribute to any element you want the user
to be able to pick up. Text, links, and images are draggable by default in
most browsers; other elements need it set explicitly.
draggable-basic.html
<ul id="list"> <li draggable="true">Task 1</li> <li draggable="true">Task 2</li> <li draggable="true">Task 3</li> </ul>
The Core Events
Drag and drop is driven by events fired on the dragged element and on potential drop targets. Understanding which event fires where is the key to using the API correctly.
Event | Fires on | Purpose |
|---|---|---|
| The dragged element | Set up the data being dragged |
| The dragged element | Fires repeatedly while dragging (rarely used directly) |
| The drop target | Dragged item enters a potential drop zone |
| The drop target | Fires continuously while hovering — must call |
| The drop target | Dragged item leaves the drop zone |
| The drop target | The item is released over the target |
| The dragged element | Drag operation finished (dropped or cancelled) |
event.preventDefault() inside the dragover handler, the drop event will never fire on that target.The dataTransfer Object
Every drag event carries a dataTransfer object, which is how
you pass information from the drag source to the drop target — since they
are usually different elements entirely.
Method | Used in | Purpose |
|---|---|---|
|
| Store data to be read later |
|
| Read the data set at drag start |
|
| Hints which operations are allowed (e.g. "move", "copy") |
|
| Controls the drag cursor feedback shown to the user |
|
| A |
Use Case: Reordering a List
reorder-list.html
<ul id="list">
<li draggable="true">Task 1</li>
<li draggable="true">Task 2</li>
<li draggable="true">Task 3</li>
</ul>
<script>
const list = document.getElementById('list')
let draggedItem = null
list.addEventListener('dragstart', (event) => {
draggedItem = event.target
event.dataTransfer.setData('text/plain', event.target.textContent)
event.dataTransfer.effectAllowed = 'move'
})
list.querySelectorAll('li').forEach((item) => {
item.addEventListener('dragover', (event) => {
event.preventDefault() // required to allow dropping
event.dataTransfer.dropEffect = 'move'
})
item.addEventListener('drop', (event) => {
event.preventDefault()
if (event.target !== draggedItem) {
list.insertBefore(draggedItem, event.target)
}
})
})
list.addEventListener('dragend', () => {
draggedItem = null
})
</script>Before drag: Task 1, Task 2, Task 3 User drags "Task 3" onto "Task 1" After drop: Task 3, Task 1, Task 2
Use Case: File Drop Zone
The same events let you build a "drop files here" zone that accepts files dragged in from the user's operating system rather than from within the page.
file-drop-zone.html
<div id="drop-zone" style="border: 2px dashed #999; padding: 40px; text-align: center;">
Drop files here
</div>
<script>
const zone = document.getElementById('drop-zone')
zone.addEventListener('dragover', (event) => {
event.preventDefault()
zone.style.borderColor = '#2e7d32'
})
zone.addEventListener('dragleave', () => {
zone.style.borderColor = '#999'
})
zone.addEventListener('drop', (event) => {
event.preventDefault()
zone.style.borderColor = '#999'
const files = event.dataTransfer.files
Array.from(files).forEach((file) => {
console.log('Dropped file:', file.name, file.size, 'bytes')
})
})
</script>dragover and drop on window and call preventDefault() there as a safety net.Touch Devices
Quick Reference
Mark draggable elements with
draggable="true".Store data in
dragstartwithevent.dataTransfer.setData(type, value).Always call
event.preventDefault()indragoverto enable dropping.Read the data back in
dropwithevent.dataTransfer.getData(type).Use
event.dataTransfer.filesto handle files dragged from the OS.