
dnd-kit: Drag and Drop That Works Without a Mouse
Most drag-and-drop implementations are unusable by keyboard and silent to screen readers. This one ships both, and it is lighter than what it replaces.
Reorderable lists are everywhere: task boards, form builders, gallery ordering, dashboard widgets. Almost none of them can be operated without a mouse, and almost none of them say anything to a screen reader while an item moves. It is treated as an acceptable exclusion, and it is not one — it is just the default of the libraries people reached for.
Sortable list, keyboard included
import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
import { SortableContext, arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable'
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
)
<DndContext sensors={sensors} onDragEnd={({ active, over }) => {
if (over && active.id !== over.id) {
setItems((items) => arrayMove(items, items.indexOf(active.id), items.indexOf(over.id)))
}
}}>
<SortableContext items={items}>
{items.map(id => <SortableRow key={id} id={id} />)}
</SortableContext>
</DndContext>
That KeyboardSensor is the whole argument. Tab to an item, press space to lift it, arrow keys to move, space to drop, escape to cancel. It is the same interaction pattern operating systems have used for decades, and it costs two lines.
Say what happened
<DndContext accessibility={{
announcements: {
onDragStart: ({ active }) => `Picked up ${active.id}`,
onDragOver: ({ active, over }) => over && `${active.id} is over position ${over.data.current.sortable.index + 1}`,
onDragEnd: ({ active, over }) => over && `${active.id} dropped at position ${over.data.current.sortable.index + 1}`,
},
}}>
These go into a live region. Without them a screen-reader user hears silence during the one interaction where feedback matters most. Write them in the user's language — on bilingual projects this is a string that needs translating like any other.
It does not use HTML5 drag events
Which is why it works on touch, why the drag preview is a React component you style instead of a browser-generated ghost image, and why the behaviour is the same across browsers rather than the same only on the one you tested.
The honest caveat
Drag is a fine-motor interaction. Even done perfectly, some people cannot use it. Anywhere ordering actually matters, keep a second route to the same outcome — a "move up / move down" control in a row menu, or a numeric position field. dnd-kit makes the drag path good; it does not remove your obligation to have a non-drag path.
Why I use it on admin work
Client dashboards are where reordering lives, and admin tools are where accessibility gets deprioritised because "it is only for staff". Staff include people who use screen readers. The keyboard path here is close to free, so there is no argument left for skipping it.
Resources
- Repo: clauderic/dnd-kit
- Docs: docs.dndkit.com
- Video walkthroughs: YouTube: dnd-kit react sortable tutorial
- Related: Radix Primitives: behaviour without the look
Need this built properly?
I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.


