Plaintext Engineering
0%

🗂️ 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?

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:

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