Kanban Board
Build a drag-and-drop kanban board for task management
What you'll learn
Implement drag-and-drop for card reordering
Build a three-column board (To Do, In Progress, Done)
Persist board state across page reloads
Add card creation, editing, and deletion
Implement column-to-column movement with state updates
Handle loading states and optimistic updates
A drag-and-drop project management board with columns and cards.
Requirements
- Columns for task stages (To Do, In Progress, Done)
- Add tasks to any column
- Drag tasks between columns
- Edit task details
- Delete tasks
- Persist state (localStorage)
Starter
"use client"
import { useState } from "react"
interface Task {
id: string
title: string
description: string
}
interface Column {
id: string
title: string
tasks: Task[]
}
export default function KanbanBoard() {
const [columns, setColumns] = useState<Column[]>([
{ id: "todo", title: "To Do", tasks: [] },
{ id: "progress", title: "In Progress", tasks: [] },
{ id: "done", title: "Done", tasks: [] },
])
// TODO: implement addTask, moveTask, editTask, deleteTask
return (
<div className="flex gap-4 overflow-x-auto p-4">
{columns.map((column) => (
<div key={column.id} className="w-72 shrink-0 rounded-lg bg-muted p-4">
<h2 className="mb-3 font-semibold">{column.title}</h2>
{column.tasks.map((task) => (
<div
key={task.id}
className="mb-2 rounded border bg-background p-3"
>
<p className="font-medium">{task.title}</p>
{task.description && (
<p className="mt-1 text-sm text-muted-foreground">
{task.description}
</p>
)}
</div>
))}
</div>
))}
</div>
)
}
Next Steps
Key Takeaways
Drag and drop with HTML5 DnD API or libraries like dnd-kit enables intuitive card reordering
A three-column board state can be one array with status field per card or three separate arrays per column
localStorage persistence requires serializing the entire board state and restoring it on mount
Column-to-column movement is a state transfer: remove from source status, add to target status
Optimistic updates update the UI immediately and revert on API failure for a responsive feel