Blox/
Kanban Board
A drag-and-drop Kanban board for project management with columns, task cards, priority badges, and assignee avatars. Perfect for agile workflows.
Drag & DropTask CardsPriority TagsAssignees
Installation
Terminal
TSXReactTailwind
npx @uiblox/cli add kanban-boardPreview
Layout:
Project Board
To Do
2Design
Design dashboard
high
EJ
Backend
Implement auth
medium
MC
In Progress
1Feature
Build notifications
mediumDec 12
DK
Done
1Setup
Setup repo
low
SA
Use Cases
Project Management
Track tasks through different stages from backlog to completion.
Sprint Planning
Organize sprint backlogs and track team progress in agile workflows.
Personal Tasks
Manage personal to-do lists with visual progress tracking.
Content Pipeline
Track content from ideation through review to publishing.
Source Code
kanban-board.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191"use client"; import { useState } from "react"; import { cn } from "@/lib/utils"; interface Task { id: string; title: string; description?: string; priority: "low" | "medium" | "high"; assignee?: { name: string; avatar: string }; tags?: string[]; dueDate?: string; } interface Column { id: string; title: string; color: string; tasks: Task[]; } const initialColumns: Column[] = [ { id: "backlog", title: "Backlog", color: "bg-slate-500", tasks: [ { id: "1", title: "Research competitor features", priority: "low", tags: ["Research"], assignee: { name: "Sarah", avatar: "SA" } }, { id: "2", title: "Update documentation", priority: "medium", tags: ["Docs"] }, ], }, { id: "todo", title: "To Do", color: "bg-purple-500", tasks: [ { id: "3", title: "Design new dashboard layout", description: "Create wireframes for the analytics dashboard", priority: "high", tags: ["Design", "UI"], assignee: { name: "Emily", avatar: "EJ" }, dueDate: "Dec 15" }, { id: "4", title: "Implement user authentication", priority: "high", tags: ["Backend"], assignee: { name: "Michael", avatar: "MC" } }, ], }, { id: "in-progress", title: "In Progress", color: "bg-cyan-500", tasks: [ { id: "5", title: "Build notification system", description: "Real-time notifications with WebSocket", priority: "medium", tags: ["Feature"], assignee: { name: "David", avatar: "DK" }, dueDate: "Dec 12" }, ], }, { id: "done", title: "Done", color: "bg-emerald-500", tasks: [ { id: "6", title: "Setup project repository", priority: "low", tags: ["Setup"], assignee: { name: "Sarah", avatar: "SA" } }, { id: "7", title: "Create design system", priority: "medium", tags: ["Design"], assignee: { name: "Emily", avatar: "EJ" } }, ], }, ]; export function KanbanBoard() { const [columns, setColumns] = useState(initialColumns); const [draggedTask, setDraggedTask] = useState<{ task: Task; columnId: string } | null>(null); const handleDragStart = (task: Task, columnId: string) => { setDraggedTask({ task, columnId }); }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); }; const handleDrop = (targetColumnId: string) => { if (!draggedTask) return; setColumns((prev) => { const newColumns = prev.map((col) => { if (col.id === draggedTask.columnId) { return { ...col, tasks: col.tasks.filter((t) => t.id !== draggedTask.task.id) }; } if (col.id === targetColumnId) { return { ...col, tasks: [...col.tasks, draggedTask.task] }; } return col; }); return newColumns; }); setDraggedTask(null); }; const getPriorityColor = (priority: Task["priority"]) => { switch (priority) { case "high": return "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"; case "medium": return "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"; case "low": return "bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400"; } }; return ( <div className="p-6"> <div className="flex items-center justify-between mb-6"> <div> <h2 className="text-2xl font-bold text-slate-900 dark:text-white">Project Board</h2> <p className="text-slate-500">Track tasks and progress</p> </div> <button className="px-4 py-2.5 bg-purple-600 hover:bg-purple-700 text-white font-medium rounded-xl flex items-center gap-2"> <PlusIcon /> Add Task </button> </div> <div className="flex gap-6 overflow-x-auto pb-6"> {columns.map((column) => ( <div key={column.id} onDragOver={handleDragOver} onDrop={() => handleDrop(column.id)} className="flex-shrink-0 w-80" > {/* Column Header */} <div className="flex items-center gap-3 mb-4"> <div className={`w-3 h-3 rounded-full ${column.color}`} /> <h3 className="font-semibold text-slate-900 dark:text-white">{column.title}</h3> <span className="px-2 py-0.5 text-xs font-medium bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 rounded-full"> {column.tasks.length} </span> </div> {/* Tasks */} <div className="space-y-3"> {column.tasks.map((task) => ( <div key={task.id} draggable onDragStart={() => handleDragStart(task, column.id)} className="p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 cursor-grab active:cursor-grabbing hover:shadow-md transition-shadow" > {/* Tags */} {task.tags && task.tags.length > 0 && ( <div className="flex flex-wrap gap-1.5 mb-2"> {task.tags.map((tag) => ( <span key={tag} className="px-2 py-0.5 text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 rounded"> {tag} </span> ))} </div> )} {/* Title */} <h4 className="font-medium text-slate-900 dark:text-white mb-1">{task.title}</h4> {/* Description */} {task.description && ( <p className="text-sm text-slate-500 mb-3 line-clamp-2">{task.description}</p> )} {/* Footer */} <div className="flex items-center justify-between"> <div className="flex items-center gap-2"> <span className={`px-2 py-0.5 text-xs font-medium rounded ${getPriorityColor(task.priority)}`}> {task.priority} </span> {task.dueDate && ( <span className="flex items-center gap-1 text-xs text-slate-500"> <CalendarIcon /> {task.dueDate} </span> )} </div> {task.assignee && ( <div className="w-7 h-7 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-white text-xs font-semibold"> {task.assignee.avatar} </div> )} </div> </div> ))} {/* Add Task Button */} <button className="w-full p-3 border-2 border-dashed border-slate-200 dark:border-slate-700 rounded-xl text-slate-500 hover:border-purple-500 hover:text-purple-500 transition-colors flex items-center justify-center gap-2"> <PlusIcon /> Add Task </button> </div> </div> ))} </div> </div> ); } function PlusIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>; } function CalendarIcon() { return <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>; }