🗂️ Build a Drag & Drop Kanban Board in React with dnd-kit
Jul 3, 2025 • 3 min read

Creating a drag & drop Kanban board is a great way to learn state management and interaction patterns in React. This tutorial uses the lightweight and modular dnd-kit library to implement draggable tasks and droppable columns.
Why dnd-kit?
- Built for React: Hook-based APIs for easy integration.
- Accessible: Keyboard and screen reader support.
- Modular and extensible: Use only what you need.
- Lightweight (~10kb): Zero dependencies.
- Multiple inputs: Pointer, touch, keyboard, and custom sensors.
1) Install dnd-kit
npm install @dnd-kit/core
2) Wrap your board with DndContext
The DndContext
component manages drag-and-drop state at the top level.
import React, { useState } from 'react';
import { DndContext } from '@dnd-kit/core';
function KanbanBoard() {
const [items, setItems] = useState({
todo: ['Task 1', 'Task 2'],
inProgress: ['Task 3'],
done: ['Task 4'],
});
function handleDragEnd(event) {
const { active, over } = event;
if (!over) return;
// Logic for moving items between columns will go here
}
return (
<DndContext onDragEnd={handleDragEnd}>
{/* Columns and tasks components */}
</DndContext>
);
}
export default KanbanBoard;
3) Make tasks draggable
Use useDraggable
to turn a component into a draggable element.
import { useDraggable } from '@dnd-kit/core';
function DraggableTask({ id, children }) {
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({ id });
const style = {
transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : undefined,
opacity: isDragging ? 0.5 : 1,
padding: '8px',
margin: '4px',
border: '1px solid #ccc',
borderRadius: '4px',
backgroundColor: 'white',
cursor: 'grab',
};
return (
<div ref={setNodeRef} style={style} {...listeners} {...attributes}>
{children}
</div>
);
}
4) Make columns droppable
Use useDroppable
to define valid drop targets for tasks.
import { useDroppable } from '@dnd-kit/core';
function DroppableColumn({ id, children }) {
const { isOver, setNodeRef } = useDroppable({ id });
const style = {
padding: '16px',
margin: '16px',
backgroundColor: isOver ? '#f0f0f0' : '#e2e2e2',
borderRadius: '8px',
minWidth: '200px',
minHeight: '200px',
};
return (
<div ref={setNodeRef} style={style}>
{children}
</div>
);
}
5) Wire up columns and tasks
Render columns and tasks, and update state on drag end to move a task from one column to another.
import { DndContext } from '@dnd-kit/core';
function KanbanBoard() {
const [items, setItems] = useState({
todo: ['task-1', 'task-2'],
inProgress: ['task-3'],
done: ['task-4'],
});
function handleDragEnd(event) {
const { active, over } = event;
if (!over) return;
const activeId = active.id;
const overId = over.id;
// Find the column the active task belongs to
let sourceColumn = null;
Object.entries(items).forEach(([columnId, tasks]) => {
if (tasks.includes(activeId)) {
sourceColumn = columnId;
}
});
const destinationColumn = overId;
if (sourceColumn && destinationColumn && sourceColumn !== destinationColumn) {
const sourceTasks = items[sourceColumn].filter((id) => id !== activeId);
const destinationTasks = [...items[destinationColumn], activeId];
setItems({
...items,
[sourceColumn]: sourceTasks,
[destinationColumn]: destinationTasks,
});
}
}
return (
<DndContext onDragEnd={handleDragEnd}>
<div style={{ display: 'flex', justifyContent: 'space-around' }}>
{Object.entries(items).map(([columnId, tasks]) => (
<DroppableColumn key={columnId} id={columnId}>
<h3>{columnId}</h3>
{tasks.map((taskId) => (
<DraggableTask key={taskId} id={taskId}>
{taskId}
</DraggableTask>
))}
</DroppableColumn>
))}
</div>
</DndContext>
);
}
6) Polish and extend
Consider:
- Drag handles to restrict drag areas
- Smooth transitions/animations
- Nested droppable regions
- Additional keyboard shortcuts (accessibility is supported out of the box)
Summary
With dnd-kit, you get a small, accessible, and flexible toolkit to build drag-and-drop UIs in React. This Kanban board demonstrates the core patterns: draggable items, droppable zones, and state updates on drag end.
Sources
Related articles


