javascript 34 lines · 8 steps

Drag-to-reorder lists with the HTML5 drag API

A single delegated handler set turns any list into a drag-to-reorder widget that reports its new order.

Explained by highlit
1function makeReorderableList(listEl, onReorder) {
2 let draggedItem = null;
3 
4 listEl.addEventListener('dragstart', (e) => {
5 const item = e.target.closest('[draggable="true"]');
6 if (!item) return;
7 draggedItem = item;
8 e.dataTransfer.effectAllowed = 'move';
9 e.dataTransfer.setData('text/plain', item.dataset.id);
10 requestAnimationFrame(() => item.classList.add('dragging'));
11 });
12 
13 listEl.addEventListener('dragover', (e) => {
14 e.preventDefault();
15 e.dataTransfer.dropEffect = 'move';
16 const target = e.target.closest('[draggable="true"]');
17 if (!target || target === draggedItem) return;
18 
19 const rect = target.getBoundingClientRect();
20 const after = e.clientY > rect.top + rect.height / 2;
21 listEl.insertBefore(draggedItem, after ? target.nextSibling : target);
22 });
23 
24 listEl.addEventListener('dragend', () => {
25 if (!draggedItem) return;
26 draggedItem.classList.remove('dragging');
27 draggedItem = null;
28 const order = [...listEl.querySelectorAll('[draggable="true"]')]
29 .map((el) => el.dataset.id);
30 onReorder(order);
31 });
32 
33 listEl.addEventListener('drop', (e) => e.preventDefault());
34}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Attaching listeners to the container instead of each item lets one closure track drag state across the whole list.
  2. 2Comparing the cursor's Y against an element's vertical midpoint decides whether to insert before or after it.
  3. 3Reading the live DOM order at dragend produces the canonical result without tracking moves manually.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Drag-to-reorder lists with the HTML5 drag API — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code