HTML Drag and Drop API Tutorial: Complete Guide with Examples for Beginners
HTML Tutorials · HTML Drag and Drop API Tag · Introduction
How to Use the HTML Drag and Drop API: Complete Beginner to Advanced Tutorial
🔹 HTML Drag and Drop API allows you to move elements around the page using your mouse (or touch). It's easy to useful for building custom UIs like sortable lists, file uploads, or drag-to-organize interfaces. Here's a basic example.
🔹 The API is built into the browser itself, so there is nothing to install: you mark an element as draggable, listen for a few events, and decide what should happen when something is dropped. Because it uses ordinary HTML attributes and JavaScript events, it works in all major desktop browsers without any library or framework.
🔑 Key Parts
✅ draggable="true" — makes the element draggable.
✅ ondragstart, ondrop, ondragover — event handlers.
✅ dataTransfer.setData() and getData() — to move elements.
How Drag and Drop Works: The Big Picture
🔹 Every drag-and-drop interaction involves two roles: the drag source (the element being picked up) and the drop target (the element that receives it). The browser fires a sequence of events on each of them as the pointer moves, and your job is to respond to just the events you care about. In the simplest case that means three things: store something in dataTransfer when the drag starts, tell the browser the target accepts drops, and read the stored data back when the drop happens.
Drag and Drop Events Reference
| Event | Fires On | When It Fires |
|---|---|---|
| dragstart | Drag source | The user starts dragging the element |
| drag | Drag source | Repeatedly while the element is being dragged |
| dragend | Drag source | The drag finishes, whether or not it was dropped successfully |
| dragenter | Drop target | A dragged item first enters the target's area |
| dragover | Drop target | Repeatedly while a dragged item is over the target |
| dragleave | Drop target | A dragged item leaves the target's area |
| drop | Drop target | The item is released over a valid target |
🔹 The three you'll use in almost every project are dragstart, dragover, and drop. The others, especially dragenter and dragleave, are mostly for visual feedback such as highlighting a drop zone.
✅ Basic Example of HTML Drag and Drop
<!DOCTYPE html>
<html>
<head>
<title>Basic Drag and Drop</title>
<style>
#dragme {
width: 100px;
height: 100px;
background-color: #3498db;
color: white;
text-align: center;
line-height: 100px;
cursor: move;
}
#dropzone {
width: 200px;
height: 150px;
border: 2px dashed #ccc;
margin-top: 20px;
}
</style>
</head>
<body>
<div id="dragme" draggable="true">Drag me</div>
<div id="dropzone">Drop here</div>
<script>
const dragme = document.getElementById("dragme");
const dropzone = document.getElementById("dropzone");
dragme.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text", e.target.id);
});
dropzone.addEventListener("dragover", (e) => {
e.preventDefault();
});
dropzone.addEventListener("drop", (e) => {
e.preventDefault();
const data = e.dataTransfer.getData("text");
dropzone.appendChild(document.getElementById(data));
});
</script>
</body>
</html>
Understanding the Basic Example Step by Step
- Mark the element draggable: draggable="true" on the blue box tells the browser it may be picked up. Images and links are draggable by default, but ordinary elements like a <div> need this attribute.
- Store what is being dragged: in dragstart, setData("text", e.target.id) saves the element's id into the drag's data store so the drop handler can find it later.
- Allow dropping: browsers refuse drops by default, so dragover calls e.preventDefault() to mark the zone as a valid target.
- Handle the drop: in drop, call preventDefault() again (to stop the browser from trying to open the data as a link), read the id back with getData("text"), and move the element with appendChild.
✅ Example 2: Reordering List Items
- Item 1
- Item 2
- Item 3
<!DOCTYPE html>
<html>
<head>
<title>Reorder List</title>
<style>
li {
padding: 10px;
border: 1px solid #ccc;
margin: 5px;
cursor: move;
list-style: none;
}
</style>
</head>
<body>
<ul id="list">
<li draggable="true" id="item1">Item 1</li>
<li draggable="true" id="item2">Item 2</li>
<li draggable="true" id="item3">Item 3</li>
</ul>
<script>
let dragged;
document.querySelectorAll("li").forEach((item) => {
item.addEventListener("dragstart", (e) => {
dragged = e.target;
});
item.addEventListener("dragover", (e) => {
e.preventDefault();
});
item.addEventListener("drop", (e) => {
e.preventDefault();
const list = document.getElementById("list");
list.insertBefore(dragged, e.target.nextSibling);
});
});
</script>
</body>
</html>
🔹 Instead of storing an id in dataTransfer, this example keeps the dragged element in an ordinary JavaScript variable. That works fine when everything lives on the same page. When the drop lands on an item, insertBefore(dragged, e.target.nextSibling) places the dragged item just after the one it was dropped on, which is what makes the list reorder.
✅ Example 3: Drag Images into Gallery
<!DOCTYPE html>
<html>
<head>
<title>Drag Images</title>
<style>
#gallery, #dropbox {
border: 2px dashed gray;
padding: 20px;
min-height: 150px;
}
img {
width: 100px;
margin: 10px;
cursor: move;
}
</style>
</head>
<body>
<div id="gallery">
<img src="https://via.placeholder.com/100" draggable="true" id="img1">
<img src="https://via.placeholder.com/100/ff4444" draggable="true" id="img2">
</div>
<div id="dropbox">Drop images here</div>
<script>
const imgs = document.querySelectorAll("img");
const dropbox = document.getElementById("dropbox");
imgs.forEach(img => {
img.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text", e.target.id);
});
});
dropbox.addEventListener("dragover", (e) => {
e.preventDefault();
});
dropbox.addEventListener("drop", (e) => {
e.preventDefault();
const data = e.dataTransfer.getData("text");
const draggedImg = document.getElementById(data);
dropbox.appendChild(draggedImg);
});
</script>
</body>
</html>
🔹 The live demo above uses small built-in placeholder pictures so it always displays, while the code sample keeps the original via.placeholder.com addresses. Swap in your own image paths when you copy it.
Adding Visual Feedback to a Drop Zone
🔹 A drop zone that stays visually silent feels broken, because users cannot tell whether a drop will work. The dragenter and dragleave events let you highlight the zone while an item hovers over it. Add a CSS class on entry, and remove it on leave and on drop.
const zone = document.getElementById("dropzone");
zone.addEventListener("dragenter", () => zone.classList.add("drag-over"));
zone.addEventListener("dragleave", () => zone.classList.remove("drag-over"));
zone.addEventListener("drop", () => zone.classList.remove("drag-over"));
🔹 One well-known quirk: dragleave also fires when the pointer moves onto a child element inside the zone, which can make the highlight flicker. If that happens, either keep the zone free of child elements, or track a counter that goes up on dragenter and down on dragleave, removing the highlight only when it returns to zero.
Dropping Files from the Computer
🔹 Drag and drop is not limited to elements on the page. When a user drags files from their desktop onto a drop zone, the files arrive in event.dataTransfer.files. This is the foundation of "drag your files here to upload" interfaces.
dropzone.addEventListener("dragover", (e) => e.preventDefault());
dropzone.addEventListener("drop", (e) => {
e.preventDefault();
const files = e.dataTransfer.files;
for (const file of files) {
console.log(file.name, file.type, file.size);
}
});
🔹 Each entry is a standard File object, so you can read it with FileReader, show an image preview, or send it to a server with fetch and FormData. Always check the file type and size before using it.
dataTransfer Properties and Methods
| Member | Purpose |
|---|---|
| setData(type, data) | Stores data for the drag, typically in dragstart |
| getData(type) | Reads stored data back, typically in drop |
| effectAllowed | Which operations the source permits: copy, move, link, or all |
| dropEffect | The operation the target chooses, which changes the cursor icon |
| files | A list of files being dragged from the operating system |
| setDragImage(el, x, y) | Replaces the default ghost image shown under the cursor |
Example 4: A Two-Column Task Board
🔹 Once the basic pattern is clear, a task board is a natural next step, and it uses exactly the same three ideas: a draggable card, a column that allows drops, and a drop handler that moves the card. The difference is that there are now several drop targets, so the handler needs to work out which column received the card.
const columns = document.querySelectorAll(".board-col");
document.querySelectorAll(".card-item").forEach((card) => {
card.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text", e.target.id);
});
});
columns.forEach((col) => {
col.addEventListener("dragover", (e) => e.preventDefault());
col.addEventListener("drop", (e) => {
e.preventDefault();
const id = e.dataTransfer.getData("text");
col.appendChild(document.getElementById(id));
});
});
🔹 Notice that the same handler is attached to every column with forEach. Inside it, col always refers to the column that received the drop, so col.appendChild(...) moves the card to the right place without any extra logic. This one-handler-many-targets approach scales to as many columns as you need, which is how boards with "To Do", "In Progress", "Review" and "Done" lanes are built.
Choosing Between Native Drag and Drop, Pointer Events, and Libraries
🔹 The native API is not the only way to make things draggable, and it is worth knowing when to use something else. The right choice depends mostly on whether you need touch support and how much visual control you want over the dragged item.
| Approach | Strengths | Weaknesses |
|---|---|---|
| Native HTML Drag and Drop | Built in, no dependencies, supports dragging files from the desktop, works between windows | Weak touch support, limited control over the drag image, some quirks with dragleave |
| Pointer Events (custom) | Works with mouse, touch, and pen in one code path; full control over movement and animation | More code to write; no built-in file dropping |
| Drag and drop library | Handles touch, sorting, animation, and accessibility for you | Extra file size and a dependency to maintain |
🔹 For simple desktop-focused features such as reordering a short list or accepting file uploads, the native API is usually the best fit. For a polished, mobile-friendly sortable interface with smooth animation, a library or a Pointer Events approach is often less painful than fighting the native API's limitations.
Customizing the Drag Image and Cursor
🔹 While an item is being dragged, the browser shows a semi-transparent copy of it under the cursor. You can replace that image with setDragImage, and you can influence the cursor icon with effectAllowed and dropEffect. Setting these correctly gives users a strong hint about what will happen when they let go: a "copy" cursor suggests the original stays in place, while "move" suggests it will be relocated.
item.addEventListener("dragstart", (e) => {
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text", e.target.id);
// Use a custom element as the drag preview
const preview = document.getElementById("custom-preview");
e.dataTransfer.setDragImage(preview, 10, 10);
});
target.addEventListener("dragover", (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
});
🔹 The two numbers passed to setDragImage are the horizontal and vertical offset of the cursor within the preview image. The preview element must be visible in the document, or at least already rendered, when dragstart fires, otherwise the browser cannot capture it.
Inline Handlers vs addEventListener
🔹 The Key Parts list near the top mentions ondragstart, ondragover, and ondrop. These are inline event-handler attributes, and they are the way many older tutorials wire up drag and drop, by writing the handler directly in the HTML tag. The examples in this tutorial use addEventListener instead, and both approaches do exactly the same job.
<!-- Inline handler style -->
<div id="box" draggable="true" ondragstart="event.dataTransfer.setData('text', this.id)">Drag me</div>
<div ondragover="event.preventDefault()" ondrop="event.preventDefault(); this.appendChild(document.getElementById(event.dataTransfer.getData('text')))">Drop here</div>
🔹 Inline handlers are compact and fine for tiny demos, but addEventListener is the better habit for real projects. It keeps your HTML clean, lets you attach more than one listener to the same event, makes handlers easy to remove later with removeEventListener, and works well with a strict Content Security Policy, which blocks inline handlers entirely on many sites.
Security and Validation of Dropped Data
🔹 Anything a user can drop onto your page is untrusted input. Text dragged in from another website, a file from the desktop, or even HTML copied from a document can contain unexpected content. Treat dropped data the same way you would treat a form field: validate its type and size, and never insert dropped text into the page with innerHTML, since that would let a malicious snippet run as HTML or script. Use textContent instead, which displays the value as plain text.
🔹 For files, check file.type and file.size before doing anything with them, and remember that the browser-reported type can be spoofed, so any real upload must also be validated again on the server.
Browser Support
🔹 The HTML Drag and Drop API is supported by all current desktop browsers, including Chrome, Firefox, Safari, and Edge, which means you can rely on it for desktop features without a fallback. Support on mobile browsers is partial and varies between operating system versions, which is why the earlier advice about testing on real devices matters. As a general rule, ship the native version for desktop users and provide a touch or keyboard alternative where the feature is important.
Mobile and Touch Support
🔹 The native HTML Drag and Drop API was designed around a mouse, and its support on touch screens is limited and inconsistent. On many phones and tablets, pressing and dragging an element simply scrolls the page instead of starting a drag. If your audience includes mobile users, you have two realistic options: use the Pointer Events or Touch Events APIs to build the same behavior yourself, or use a small library that already handles touch. Either way, always test on a real device rather than trusting a desktop browser's mobile emulator.
Accessibility: Don't Make Drag the Only Option
🔹 Drag and drop is difficult or impossible for people who use keyboards, screen readers, or voice control, so treat it as an enhancement rather than the only way to complete a task. For a reorderable list, add "Move up" and "Move down" buttons. For a file upload zone, include a normal file input as well. This is not only good practice; it also makes your interface easier to use for everyone on a trackpad, a touch screen, or with a temporary injury.
Common Mistakes with the Drag and Drop API
- 🔹 Forgetting preventDefault() in dragover: the most common bug. Without it the browser never treats the element as a drop target, and the drop event never fires.
- 🔹 Forgetting draggable="true": ordinary elements cannot be dragged without it.
- 🔹 Missing ids on dragged elements: if you store e.target.id and the element has no id, getData returns an empty string and the drop silently does nothing.
- 🔹 Selecting every <li> or <img> on the page: a broad selector like querySelectorAll("li") also catches menu items and other unrelated elements. Scope it to your own container.
- 🔹 Skipping preventDefault() in drop: some browsers then try to open the dropped data, for example navigating to a dropped file.
- 🔹 Assuming touch devices work: the native API often does nothing on phones, so a mobile fallback is needed.
HTML Drag and Drop Best Practices
- 🔹 Give every draggable element a unique id, or another reliable way to find it again on drop.
- 🔹 Highlight the drop zone on dragenter so users know where they can drop.
- 🔹 Set a clear cursor, such as cursor: move, on draggable items.
- 🔹 Offer a keyboard-friendly alternative for anything that matters, such as buttons for moving items.
- 🔹 Validate dropped files by type and size before processing them.
- 🔹 Scope your selectors to a specific container so unrelated page elements are never affected.
- 🔹 Test on real touch devices, and use a touch-aware fallback where needed.
Try It Yourself (Copy This Code and Paste. See how it works). Paste any of the examples above into the live playground below.
Live Code Preview
Practice Exercises: Test the Drag and Drop API
- Rebuild the basic example, then change the drag source to a paragraph of text.
- Add dragenter and dragleave handlers that highlight the drop zone.
- Extend the list example with "Move up" and "Move down" buttons as a keyboard alternative.
- Build a two-column "To do" and "Done" board where items can be dragged between the columns.
- Create a drop zone that accepts only image files and shows a thumbnail of each one.
Frequently Asked Questions About the HTML Drag and Drop API
What is HTML Drag and Drop API?
It is a feature that allows users to drag elements and drop them into another area on a webpage.
What are drag and drop events?
Common events include dragstart, dragover, and drop.
Is drag and drop supported on mobile?
It has limited support on mobile devices and often requires additional JavaScript handling.
Why do I need to call preventDefault on dragover?
By default the browser does not allow dropping onto most elements. Calling preventDefault on dragover tells the browser the element is a valid drop target.
Can I drag and drop files from my computer?
Yes. When files are dropped onto a drop zone, they are available through event.dataTransfer.files.
Conclusion
🔹 The HTML Drag and Drop API turns ordinary elements into interactive pieces of a page with only a handful of lines of JavaScript: mark the element with draggable="true", save its identity in dragstart, allow the drop in dragover, and finish the job in drop. From that foundation you can build sortable lists, image galleries, kanban boards, and file upload zones. Remember the two most common stumbling blocks, calling preventDefault() and providing keyboard and touch alternatives, and work through the practice exercises to make the pattern second nature.