A Drag and Drop Kanban Board is a visual project management tool that allows users to organize tasks into different stages (columns) and move them between these stages using an interactive drag-and-drop interface. Each task is typically represented as a 'card,' and the stages are 'columns' such as 'To Do,' 'In Progress,' and 'Done.' This functionality makes task management highly intuitive, dynamic, and reflective of a project's real-time status.
In React, building such a board involves several key aspects:
1. State Management: The entire board's data, including the definition of columns and the specific cards within each column, must be managed within the React component's state. This state will be the single source of truth and needs to be updated consistently when cards are dragged and dropped.
2. Drag and Drop Library: While it's technically possible to implement drag-and-drop functionality using native DOM APIs, it's highly recommended to leverage a specialized library. These libraries abstract away the complexities of browser interactions, provide better accessibility, and often include performance optimizations. Popular choices for React include `react-beautiful-dnd` and `dnd-kit`.
3. Core Components: The structure of a Kanban board typically breaks down into three main component types:
* Board (Context): The top-level component that encapsulates the entire Kanban structure. It's responsible for managing the global board state and often provides the `DragDropContext` from the chosen library.
* Column (Droppable Area): Each column represents a distinct stage or status. It acts as a 'droppable' area where cards can be placed.
* Card (Draggable Item): Each individual task is represented by a card, which is a 'draggable' item that can be moved within its current column or transferred to a different column.
4. `onDragEnd` Event Handling: This is the most critical part of the logic. When a drag operation concludes, the `onDragEnd` callback (provided by the drag-and-drop library) is triggered. This function receives an object containing crucial information about the drag event, including the `source` (where the draggable item originated) and the `destination` (where it was dropped). Based on this information, the component's state must be updated to reflect the card's new position. This involves:
* Removing the card's ID from its original column's `taskIds` array.
* Inserting the card's ID into the new column's `taskIds` array at the correct index.
* Handling edge cases, such as when a card is dropped outside any valid droppable area.
The example code provided below uses `react-beautiful-dnd` to demonstrate a basic Kanban board. It defines a `DragDropContext` to encapsulate the drag-and-drop functionality, `Droppable` components for each column, and `Draggable` components for individual cards. The `onDragEnd` function contains the necessary logic to update the board's state when a card is moved, handling both reordering within a column and moving cards between different columns.
Example Code
import React, { useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
// Initial data for the Kanban board
// This structure keeps column order separate from column details for easy iteration
const initialColumnOrder = ['column-1', 'column-2', 'column-3'];
const initialBoardData = {
'column-1': {
id: 'column-1',
title: 'To Do',
taskIds: ['task-1', 'task-2'],
},
'column-2': {
id: 'column-2',
title: 'In Progress',
taskIds: ['task-3'],
},
'column-3': {
id: 'column-3',
title: 'Done',
taskIds: ['task-4'],
},
};
const initialTasks = {
'task-1': { id: 'task-1', content: 'Set up development environment' },
'task-2': { id: 'task-2', content: 'Design database schema' },
'task-3': { id: 'task-3', content: 'Implement user authentication' },
'task-4': { id: 'task-4', content: 'Deploy to staging server' },
};
// Helper function to reorder an array without mutation
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
// Helper function to move a task from one list to another
const move = (sourceList, destinationList, droppableSource, droppableDestination) => {
const sourceClone = Array.from(sourceList);
const destClone = Array.from(destinationList);
const [removed] = sourceClone.splice(droppableSource.index, 1);
destClone.splice(droppableDestination.index, 0, removed);
const result = {};
result[droppableSource.droppableId] = sourceClone;
result[droppableDestination.droppableId] = destClone;
return result;
};
const KanbanBoard = () => {
const [boardData, setBoardData] = useState(initialBoardData);
const [tasks, setTasks] = useState(initialTasks); // Tasks details are kept separate
const onDragEnd = (result) => {
const { source, destination } = result;
// 1. Dropped outside any droppable area
if (!destination) {
return;
}
// 2. Dropped in the same column
if (source.droppableId === destination.droppableId) {
const column = boardData[source.droppableId];
const reorderedTaskIds = reorder(
column.taskIds,
source.index,
destination.index
);
const newColumn = {
...column,
taskIds: reorderedTaskIds,
};
setBoardData({
...boardData,
[newColumn.id]: newColumn,
});
} else {
// 3. Moved to a different column
const sourceColumn = boardData[source.droppableId];
const destinationColumn = boardData[destination.droppableId];
const movedTaskIds = move(
sourceColumn.taskIds,
destinationColumn.taskIds,
source,
destination
);
const newSourceColumn = {
...sourceColumn,
taskIds: movedTaskIds[sourceColumn.id],
};
const newDestinationColumn = {
...destinationColumn,
taskIds: movedTaskIds[destinationColumn.id],
};
setBoardData({
...boardData,
[newSourceColumn.id]: newSourceColumn,
[newDestinationColumn.id]: newDestinationColumn,
});
}
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<div style={styles.boardContainer}>
{initialColumnOrder.map((columnId) => {
const column = boardData[columnId];
const columnTasks = column.taskIds.map(taskId => tasks[taskId]);
return (
<Droppable key={column.id} droppableId={column.id}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
style={{
...styles.column,
backgroundColor: snapshot.isDraggingOver ? '#e0e0e0' : '#f4f5f7',
}}
>
<h3 style={styles.columnTitle}>{column.title}</h3>
{columnTasks.map((task, index) => (
<Draggable key={task.id} draggableId={task.id} index={index}>
{(providedDraggable, snapshotDraggable) => (
<div
ref={providedDraggable.innerRef}
{...providedDraggable.draggableProps}
{...providedDraggable.dragHandleProps}
style={{
...styles.card,
backgroundColor: snapshotDraggable.isDragging ? '#263B4A' : 'white',
color: snapshotDraggable.isDragging ? 'white' : 'black',
boxShadow: snapshotDraggable.isDragging ? '2px 2px 10px rgba(0,0,0,0.3)' : 'none',
...providedDraggable.draggableProps.style, // Apply styles from rbd
}}
>
{task.content}
</div>
)}
</Draggable>
))}
{provided.placeholder} {/* Important for correct drag/drop spacing */}
</div>
)}
</Droppable>
);
})}
</div>
</DragDropContext>
);
};
const styles = {
boardContainer: {
display: 'flex',
justifyContent: 'center',
gap: '20px',
padding: '30px',
backgroundColor: '#0079BF', // Trello-like blue background
minHeight: '100vh',
alignItems: 'flex-start',
},
column: {
padding: '12px',
width: '300px',
minHeight: '400px',
borderRadius: '8px',
display: 'flex',
flexDirection: 'column',
border: '1px solid #dfe1e6',
boxShadow: '0 1px 0 rgba(9,30,66,.25)',
transition: 'background-color 0.2s ease',
},
columnTitle: {
marginBottom: '15px',
fontWeight: 'bold',
fontSize: '1.2em',
color: '#172B4D',
textAlign: 'center',
},
card: {
userSelect: 'none',
padding: '16px',
margin: '0 0 10px 0',
minHeight: '50px',
border: '1px solid #dfe1e6',
borderRadius: '3px',
fontSize: '0.9em',
transition: 'background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease',
wordWrap: 'break-word',
whiteSpace: 'normal',
},
};
export default KanbanBoard;








Drag and Drop Kanban Board