Drag & Drop
Native HTML5 drag and drop — draggable elements, drop targets, DataTransfer, file drops, and sorting lists.
dragleave doesn't mean "the drag left the drop zone". It fires on the drop zone every time the pointer moves onto one of its children, and the child's own dragleave bubbles up as well. Any drop zone with a label or an icon inside it gets a stream of false "left" events, so the highlight flickers. If dragleave hides the zone or resets state, it can vanish right under the cursor.
// <div id="drop-target"><p>Drop here</p></div> <- a child element inside the target
const dropTarget = document.getElementById("drop-target");
dropTarget.addEventListener("dragover", (e) => {
e.preventDefault();
dropTarget.classList.add("drag-over");
});
dropTarget.addEventListener("dragleave", (e) => {
dropTarget.classList.remove("drag-over"); // runs while the pointer is still inside
console.log("dragleave fired, pointer now over:", e.relatedTarget?.tagName);
});
// drag into the target, over the <p>, then out:
// dragleave fired, pointer now over: P <- still inside the drop target
// dragleave fired, pointer now over: DIV <- bubbled up from the <p>
// dragleave fired, pointer now over: HTML <- the only real exitMaking elements draggable
Basic drag and drop
<!-- HTML: add draggable="true" to make an element draggable -->
<div draggable="true" id="card">Drag me!</div>
<div id="drop-target">Drop here</div>const card = document.getElementById("card");
const dropTarget = document.getElementById("drop-target");
// ── DRAGGABLE ELEMENT EVENTS ──────────────────────────────────────────────
card.addEventListener("dragstart", (event) => {
// Store data to pass to the drop target:
event.dataTransfer.setData("text/plain", card.id);
event.dataTransfer.effectAllowed = "move"; // "copy", "move", "link"
card.classList.add("dragging");
});
card.addEventListener("dragend", () => {
card.classList.remove("dragging");
});
// ── DROP TARGET EVENTS ────────────────────────────────────────────────────
dropTarget.addEventListener("dragover", (event) => {
event.preventDefault(); // ← REQUIRED or drop won't fire!
event.dataTransfer.dropEffect = "move";
dropTarget.classList.add("drag-over");
});
dropTarget.addEventListener("dragleave", () => {
dropTarget.classList.remove("drag-over");
});
dropTarget.addEventListener("drop", (event) => {
event.preventDefault();
const id = event.dataTransfer.getData("text/plain");
const dragged = document.getElementById(id);
dropTarget.appendChild(dragged); // move the element
dropTarget.classList.remove("drag-over");
});The basic example above has the same pattern, so its highlight flickers as soon as the drop target contains anything. dragleave tells you where the pointer went, in event.relatedTarget. If that's still inside the drop zone, ignore the event:
const dropTarget = document.getElementById("drop-target");
dropTarget.addEventListener("dragover", (e) => {
e.preventDefault();
dropTarget.classList.add("drag-over");
});
dropTarget.addEventListener("dragleave", (e) => {
if (dropTarget.contains(e.relatedTarget)) return; // moved onto a child: still inside
dropTarget.classList.remove("drag-over");
console.log("left the drop target");
});
// same drag: one message, only when the pointer really leaves
// left the drop targetFile drop — dragging files from the desktop
dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
dropZone.classList.add("active");
});
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
dropZone.classList.remove("active");
// Access dropped files:
const files = [...e.dataTransfer.files];
files.forEach(file => {
console.log(`File: ${file.name} (${file.type}, ${(file.size/1024).toFixed(1)}KB)`);
if (file.type.startsWith("image/")) {
const reader = new FileReader();
reader.onload = (e) => { preview.src = e.target.result; };
reader.readAsDataURL(file);
}
});
});Official Sources
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.