Drag & Drop
Accessible drag and drop with dnd-kit — sortable lists, kanban boards, and custom drag interactions.
Read this when adding drag and drop features.
Useful for kanban boards, sortable lists, or reorderable items.
Why dnd-kit
dnd-kit is built for modern React with accessibility first. It uses hooks and context, supports keyboard navigation, and works with screen readers out of the box.
The modular architecture means you only import what you need. It handles sortable lists, drag between containers, and custom collision detection — all with excellent performance.
Key Features
What dnd-kit provides:
Sortable
Reorder items. Vertical, horizontal, or grid layouts with smooth animations.
Multi-Container
Drag between lists. Move items between columns, like kanban boards.
Accessible
Keyboard & screen reader. Full keyboard navigation and ARIA announcements.
Catalyst Integration
Catalyst wraps dnd-kit in reusable components:
components/vendor/dnd-kit/Sortable components ready to use
app/(examples)/examples/kanban/Kanban board example with drag and drop
Quick Start
Basic sortable list
"use client"
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core"
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from "@dnd-kit/sortable"
import { useState } from "react"
export function SortableList() {
const [items, setItems] = useState(["Item 1", "Item 2", "Item 3"])
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
)
function handleDragEnd(event) {
const { active, over } = event
if (active.id !== over?.id) {
setItems((items) => {
const oldIndex = items.indexOf(active.id)
const newIndex = items.indexOf(over.id)
return arrayMove(items, oldIndex, newIndex)
})
}
}
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map((item) => (
<SortableItem key={item} id={item} />
))}
</SortableContext>
</DndContext>
)
}Sortable item component
import { useSortable } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
function SortableItem({ id }) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id })
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
}
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<GripIcon className="h-4 w-4 cursor-grab" />
{id}
</div>
)
}Using Catalyst Wrappers
Catalyst provides pre-built sortable components:
import { Sortable, SortableItem } from "@/components/vendor/dnd-kit"
export function TaskList() {
const [tasks, setTasks] = useState([
{ id: "1", title: "Task 1" },
{ id: "2", title: "Task 2" },
])
return (
<Sortable
items={tasks}
onReorder={setTasks}
renderItem={(task) => (
<SortableItem id={task.id}>
{task.title}
</SortableItem>
)}
/>
)
}Check components/vendor/dnd-kit/ for available wrappers and their props.
Tips
Items need unique IDs. dnd-kit tracks items by ID — make sure each item has a unique identifier.
Add keyboard sensor. Include KeyboardSensor for accessibility — users can reorder with arrow keys.
Use CSS.Transform for smooth animations. The utilities package provides transform helpers.
Handle drag end, not drag move. Update state in onDragEnd to avoid performance issues.
Learn More
For AI Agents
Key rules:
- Check
components/vendor/dnd-kit/for existing wrappers first - Items must have unique ID props
- Include both PointerSensor and KeyboardSensor for accessibility
- Use
arrayMovefrom @dnd-kit/sortable for reordering - See
/examples/kanbanfor multi-container example