AniUI Academy

Build a drag-to-reorder list

mediumUI ComponentsHigh freq+220 XP

Build a list of items that can be reordered by dragging. Each item is a `<li>` that can be dragged onto any other item in the list.

On drop, the dragged item moves to the position of the drop target, and every other item shifts to preserve order.

Use the native HTML5 drag-and-drop API — `draggable`, `onDragStart`, `onDragOver`, `onDrop`.

Examples

// Initial: [Backlog, In Progress, Review, Done]
//           item-0    item-1       item-2  item-3

// Drag item-0 ("Backlog") onto item-3 ("Done")
// → [In Progress, Review, Done, Backlog]
//   item-0 is now "In Progress", item-3 is now "Backlog"

// Drop an item on itself → no change.

testids reindex after a reorder — item-0 is always whatever currently sits at position 0.

function onDrop(toIndex) {
  if (draggedFrom === null || draggedFrom === toIndex) return;
  setItems((prev) => {
    const next = [...prev];
    const [moved] = next.splice(draggedFrom, 1);
    next.splice(toIndex, 0, moved);
    return next;
  });
  setDraggedFrom(null);
}

Splice out, splice in; immutable copy so React re-renders.

What it has to do

  • The ordered list is `<ul>` with items as `<li data-testid="item-{index}">` — testid is the item's current position in the list, not its id.
  • Every item has `draggable="true"` and renders its text as plain text.
  • On drop, the dragged item moves to the drop target's position; every other item shifts accordingly.
  • After a reorder, the `data-testid="item-N"` sequence must reflect the new order (so other code and tests can address items by their current position).
  • The default drag preview is fine — no custom ghost needed.

Notes

  • `onDragOver` MUST call `e.preventDefault()` — without it, the browser refuses to fire the drop event. The single most common bug on this problem.
  • Keep the source index in React state, not `e.dataTransfer` — `dataTransfer` is write-only inside `dragstart` on some browsers and awkward to test.
  • Native HTML5 DnD doesn't work on touch devices at all — a real reorder list needs a pointer-event fallback (or a library like dnd-kit) for mobile.
  • Accessibility: provide a keyboard reorder (e.g. focus an item, press Space to pick up, arrows to move, Space to drop) — HTML5 DnD is mouse-only.
Asked atAtlassianLinearNotionShopifyMicrosoft

Stuck? The react course covers the ideas this problem is built on.

import { useState } from "react";

const INITIAL = ["Backlog", "In Progress", "Review", "Done"];

export default function App() {
  const [items, setItems] = useState(INITIAL);
  const [draggedFrom, setDraggedFrom] = useState(null);

  // TODO: implement onDragStart (remember the source index), onDragOver
  // (allow the drop), and onDrop (splice the dragged item out and in at
  // the target). Reset state on dragend.

  return (
    <ul style={{ fontFamily: "sans-serif", padding: 16, listStyle: "none" }}>
      {items.map((item, index) => (
        <li
          key={item}
          data-testid={`item-${index}`}
          draggable
          style={{ padding: 10, border: "1px solid #ddd", margin: "4px 0", cursor: "grab" }}
        >
          {item}
        </li>
      ))}
    </ul>
  );
}

4 tests · +220 XP

Discussion

Sign in to post
Sign in to join the conversation.Sign in

No comments yet. Be the first to say something.