Drag-and-Drop Sorting
Pointer-based reordering with keyboard parity, drop indicators, and optimistic persistence.
Component PatternsAdvanced
Why
Drag-and-drop is the fastest way to reorder items by spatial intuition. But pointer-only DnD excludes keyboard and touch users and is easy to get wrong (janky, no drop affordance).
A pointer + keyboard + touch solution serves everyone.
When
Use DnD for reordering within a list, kanban columns, or tree nodes. Avoid for move-between-containers with >50 items — the cognitive load beats a move-to dropdown.
How
1. Pick a lib — @dnd-kit/core (accessible, recommended) or react-beautiful-dnd (legacy)
2. Pointer — onPointerDown captures the item, drag overlay follows the cursor
3. Drop indicator — a 2px line where the item will land, computed from pointer over target
4. Keyboard — Space picks up, Arrow keys move, Space drops; announce via aria-live
5. Touch — long-press to start (avoid conflict with scroll), or a dedicated drag handle
6. Optimistic — reorder local state immediately, PATCH server in background, rollback on error
7. Constraints — lock to one axis for lists, restrict drop zones, snap to grid for kanban
Example
import { DndContext, useDraggable, useDroppable, PointerSensor, KeyboardSensor } from "@dnd-kit/core";
function SortableItem({ id, label }: { id: string; label: string }) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ id });
const style = transform ? { transform: `translate(${transform.x}px,${transform.y}px)` } : undefined;
return (
<li ref={setNodeRef} style={style} {...listeners} {...attributes}
className="cursor-grab focus:ring-2" tabIndex={0}>
{label} <span className="sr-only">press Space to drag, arrows to move</span>
</li>
);
}
function SortableList({ items, onReorder }: Props) {
return (
<DndContext sensors={[useSensor(PointerSensor), useSensor(KeyboardSensor)]}
onDragEnd={(e) => onReorder(e.active.id, e.over.id)}>
<ul role="list">
{items.map((it) => <SortableItem key={it.id} id={it.id} label={it.label} />)}
</ul>
</DndContext>
);
}
Common Problems
- ⚠Pointer-only — keyboard users can't reorder, fails WCAG 2.1.1 keyboard operable
- ⚠No drop indicator — users drop blind, items land in surprising places
- ⚠Drag conflicts with scroll — touch users can't scroll the list, every gesture starts a drag
Best Practices
- ✓Always include a KeyboardSensor — Space to pick up, arrows to move, Space to drop
- ✓Show a drop indicator line — users need to see where the item will land before releasing
- ✓Use a drag handle on touch — long-press the handle to drag, leaves the rest free to scroll