Skip to content
Back to Design Hub

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

FAQ