Skip to main content
RapidDev - Software Development Agency

How to Build Task management app with V0

Build a Trello-style task management app with V0 featuring kanban boards, drag-and-drop using dnd-kit, task assignment with avatars, due dates, priority badges, and real-time board updates. You'll create project workspaces with list and board views using Next.js and Supabase — all in about 1-2 hours.

What you'll build

  • Kanban board with draggable columns (To Do, In Progress, Review, Done) using @dnd-kit/core and @dnd-kit/sortable
  • Task cards with shadcn/ui Card showing title, Avatar for assignee, Badge for priority, and due date
  • Task detail editor Dialog with Form, Input, Textarea, Select for priority, Calendar for due date
  • Optimistic drag-and-drop that immediately updates the UI and persists via Server Action
  • Project list page and alternative list view with sortable Table columns via Tabs toggle
  • Supabase RLS ensuring project members can only access their own projects and tasks
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate10 min read1-2 hoursV0 FreeApril 2026RapidDev Engineering Team
TL;DR

Build a Trello-style task management app with V0 featuring kanban boards, drag-and-drop using dnd-kit, task assignment with avatars, due dates, priority badges, and real-time board updates. You'll create project workspaces with list and board views using Next.js and Supabase — all in about 1-2 hours.

What you're building

Task management is essential for any team — whether you are tracking product features, client deliverables, or personal goals. A kanban board gives visual clarity on what is in progress, what is blocked, and what is done.

V0 generates the kanban layout, task cards, and drag-and-drop logic from prompts. Ask V0 to add @dnd-kit/core and @dnd-kit/sortable for the drag-and-drop functionality. Supabase stores projects, columns, and tasks with position-based ordering for drag reordering.

The architecture uses Server Components for the project list, a client component for the interactive kanban board with dnd-kit, Server Actions for task mutations (create, move, update, delete), and Dialog components for the task detail editor.

Final result

A kanban task manager with drag-and-drop boards, task assignment, priority tracking, due dates, project workspaces, and real-time position persistence.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
@dnd-kitDrag and Drop

Prerequisites

  • A V0 account (free tier works for this project)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • Supabase Auth configured for user authentication
  • Basic understanding of kanban boards (columns with cards you can drag between)

Build steps

1

Set up the project and task database schema

Open V0 and create a new project. Use the Connect panel to add Supabase. Create the projects, columns, tasks, and project_members tables with position columns for drag-and-drop ordering.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Create a Supabase schema for a task management app:
3// 1. projects table: id (uuid PK), name (text), description (text), owner_id (uuid FK), created_at (timestamptz)
4// 2. columns table: id (uuid PK), project_id (uuid FK), name (text — 'To Do', 'In Progress', 'Review', 'Done'), position (int for ordering)
5// 3. tasks table: id (uuid PK), column_id (uuid FK), title (text NOT NULL), description (text), assignee_id (uuid FK nullable), priority (text DEFAULT 'medium'), due_date (date nullable), position (int for ordering within column), created_at (timestamptz), updated_at (timestamptz)
6// 4. project_members table: project_id (uuid FK), user_id (uuid FK), role (text DEFAULT 'member'), PRIMARY KEY(project_id, user_id)
7// RLS: project_members can access their project's columns and tasks.
8// Seed a sample project with 4 columns and 8 tasks across columns.

Pro tip: Use V0's prompt queuing — queue the schema prompt, then 'add @dnd-kit/core and @dnd-kit/sortable to the project', then the kanban board component prompt.

Expected result: Four tables created with RLS policies, a sample project seeded with 4 columns and 8 tasks distributed across them.

2

Build the kanban board with drag-and-drop

Create the kanban board as a client component using @dnd-kit for drag-and-drop between columns. Each column is a droppable area, each task card is draggable. Dropping a task updates its column_id and position.

components/kanban-board.tsx
1'use client'
2
3import { useState } from 'react'
4import { DndContext, closestCorners, DragEndEvent } from '@dnd-kit/core'
5import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
6import { Card, CardContent } from '@/components/ui/card'
7import { Badge } from '@/components/ui/badge'
8import { Avatar, AvatarFallback } from '@/components/ui/avatar'
9import { Button } from '@/components/ui/button'
10import { ScrollArea } from '@/components/ui/scroll-area'
11import { moveTask } from '@/app/actions/tasks'
12
13type Task = {
14 id: string
15 title: string
16 priority: string
17 assignee_id: string | null
18 due_date: string | null
19 position: number
20 column_id: string
21}
22
23type Column = {
24 id: string
25 name: string
26 position: number
27 tasks: Task[]
28}
29
30const priorityColors: Record<string, string> = {
31 high: 'bg-red-100 text-red-800',
32 medium: 'bg-yellow-100 text-yellow-800',
33 low: 'bg-green-100 text-green-800',
34}
35
36export function KanbanBoard({ initialColumns }: { initialColumns: Column[] }) {
37 const [columns, setColumns] = useState(initialColumns)
38
39 async function handleDragEnd(event: DragEndEvent) {
40 const { active, over } = event
41 if (!over) return
42
43 const taskId = active.id as string
44 const targetColumnId = over.id as string
45
46 // Optimistic update
47 setColumns((prev) => {
48 const updated = prev.map((col) => ({
49 ...col,
50 tasks: col.tasks.filter((t) => t.id !== taskId),
51 }))
52 const targetCol = updated.find((c) => c.id === targetColumnId)
53 const task = prev.flatMap((c) => c.tasks).find((t) => t.id === taskId)
54 if (targetCol && task) {
55 targetCol.tasks.push({ ...task, column_id: targetColumnId, position: targetCol.tasks.length })
56 }
57 return updated
58 })
59
60 await moveTask(taskId, targetColumnId, 0)
61 }
62
63 return (
64 <DndContext collisionDetection={closestCorners} onDragEnd={handleDragEnd}>
65 <div className="flex gap-4 overflow-x-auto p-4">
66 {columns.map((column) => (
67 <div key={column.id} className="w-72 flex-shrink-0">
68 <h3 className="font-semibold mb-3 flex items-center gap-2">
69 {column.name}
70 <Badge variant="secondary">{column.tasks.length}</Badge>
71 </h3>
72 <SortableContext items={column.tasks.map((t) => t.id)} strategy={verticalListSortingStrategy}>
73 <ScrollArea className="space-y-2">
74 {column.tasks.map((task) => (
75 <Card key={task.id} className="mb-2 cursor-grab">
76 <CardContent className="p-3">
77 <p className="font-medium text-sm">{task.title}</p>
78 <div className="flex items-center justify-between mt-2">
79 <Badge className={priorityColors[task.priority]} variant="secondary">
80 {task.priority}
81 </Badge>
82 {task.assignee_id && (
83 <Avatar className="w-6 h-6">
84 <AvatarFallback className="text-xs">U</AvatarFallback>
85 </Avatar>
86 )}
87 </div>
88 </CardContent>
89 </Card>
90 ))}
91 </ScrollArea>
92 </SortableContext>
93 <Button variant="ghost" size="sm" className="w-full mt-2">+ Add task</Button>
94 </div>
95 ))}
96 </div>
97 </DndContext>
98 )
99}

Pro tip: Use Design Mode (Option+D) to visually adjust card sizing, column widths, and the color palette for priority Badges at zero credit cost.

Expected result: A kanban board with four columns. Task cards are draggable between columns. Dropping a card immediately updates the UI and persists the change via Server Action.

3

Create Server Actions for task operations

Build Server Actions for creating tasks, moving tasks between columns, updating task details, and deleting tasks. The moveTask action updates both column_id and position atomically.

app/actions/tasks.ts
1'use server'
2
3import { createClient } from '@/lib/supabase/server'
4import { revalidatePath } from 'next/cache'
5
6export async function createTask(projectId: string, columnId: string, title: string) {
7 const supabase = await createClient()
8 const { data: maxPos } = await supabase
9 .from('tasks')
10 .select('position')
11 .eq('column_id', columnId)
12 .order('position', { ascending: false })
13 .limit(1)
14 .single()
15
16 await supabase.from('tasks').insert({
17 column_id: columnId,
18 title,
19 position: (maxPos?.position ?? 0) + 1,
20 })
21 revalidatePath(`/projects/${projectId}`)
22}
23
24export async function moveTask(taskId: string, newColumnId: string, newPosition: number) {
25 const supabase = await createClient()
26 await supabase.from('tasks').update({
27 column_id: newColumnId,
28 position: newPosition,
29 updated_at: new Date().toISOString(),
30 }).eq('id', taskId)
31}
32
33export async function updateTask(taskId: string, data: {
34 title?: string
35 description?: string
36 priority?: string
37 assignee_id?: string | null
38 due_date?: string | null
39}) {
40 const supabase = await createClient()
41 await supabase.from('tasks').update({
42 ...data,
43 updated_at: new Date().toISOString(),
44 }).eq('id', taskId)
45}
46
47export async function deleteTask(taskId: string, projectId: string) {
48 const supabase = await createClient()
49 await supabase.from('tasks').delete().eq('id', taskId)
50 revalidatePath(`/projects/${projectId}`)
51}

Expected result: Server Actions handle all task CRUD operations. moveTask updates column_id and position for drag-and-drop persistence.

4

Build the task detail editor Dialog

Create a Dialog component that opens when clicking a task card. It shows the full task details with editable fields for title, description, priority, assignee, and due date.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a task detail editor Dialog component.
3// When a user clicks a task card on the kanban board, open a shadcn/ui Dialog with:
4// - Input for task title (editable)
5// - Textarea for description
6// - Select for priority (low, medium, high)
7// - Select for assignee (list of project members with Avatar)
8// - Calendar date picker for due date
9// - Badge showing current column/status
10// - Button row: Save, Delete (with AlertDialog confirmation), Close
11// - Activity feed section showing when the task was created, moved, and edited
12// Use Server Action updateTask for saving and deleteTask for removing.
13// Use Form component for proper form handling.

Expected result: A Dialog editor with editable task fields, Calendar date picker, assignee Select with member Avatars, and Save/Delete buttons.

5

Add the project list and alternative list view

Create the project list page and add a Tabs toggle on the board page to switch between kanban board and list view. The list view shows tasks in a sortable Table.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build two views for the task management app:
3// 1. Project list page at app/projects/page.tsx:
4// - Server Component fetching all projects for the current user via project_members
5// - Card for each project showing name, description, task count, member Avatars
6// - "New Project" Button that opens a Dialog with Form for name and description
7// 2. Add Tabs to app/projects/[id]/page.tsx to toggle between Board and List views:
8// - Board tab: the KanbanBoard component
9// - List tab: Table with columns for Title, Status (column name), Priority Badge, Assignee Avatar, Due Date, Actions DropdownMenu
10// - Table rows should be sortable by clicking column headers
11// Use Server Components for data fetching, client components only for interactive elements.

Expected result: A project list page with Cards and a new project Dialog. The board page has Tabs switching between kanban board and sortable Table list view.

Complete code

app/actions/tasks.ts
1'use server'
2
3import { createClient } from '@/lib/supabase/server'
4import { revalidatePath } from 'next/cache'
5
6export async function createTask(
7 projectId: string,
8 columnId: string,
9 title: string
10) {
11 const supabase = await createClient()
12
13 const { data: maxPos } = await supabase
14 .from('tasks')
15 .select('position')
16 .eq('column_id', columnId)
17 .order('position', { ascending: false })
18 .limit(1)
19 .single()
20
21 await supabase.from('tasks').insert({
22 column_id: columnId,
23 title,
24 position: (maxPos?.position ?? 0) + 1,
25 })
26
27 revalidatePath(`/projects/${projectId}`)
28}
29
30export async function moveTask(
31 taskId: string,
32 newColumnId: string,
33 newPosition: number
34) {
35 const supabase = await createClient()
36 await supabase
37 .from('tasks')
38 .update({
39 column_id: newColumnId,
40 position: newPosition,
41 updated_at: new Date().toISOString(),
42 })
43 .eq('id', taskId)
44}
45
46export async function updateTask(
47 taskId: string,
48 projectId: string,
49 data: Record<string, unknown>
50) {
51 const supabase = await createClient()
52 await supabase
53 .from('tasks')
54 .update({ ...data, updated_at: new Date().toISOString() })
55 .eq('id', taskId)
56 revalidatePath(`/projects/${projectId}`)
57}
58
59export async function deleteTask(taskId: string, projectId: string) {
60 const supabase = await createClient()
61 await supabase.from('tasks').delete().eq('id', taskId)
62 revalidatePath(`/projects/${projectId}`)
63}

Customization ideas

Add task labels and tags

Create a labels table with name and color. Let users assign multiple labels to tasks and filter the board by label.

Add time tracking

Add a timer component to task cards that tracks time spent. Store time entries in a separate table for reporting and invoicing.

Add subtasks and checklists

Add a subtasks table linked to parent tasks. Display completion percentage on the task card with a progress bar.

Add board templates

Let users create projects from templates with predefined columns and sample tasks for common workflows (Agile, Kanban, Bug Tracking).

Common pitfalls

Pitfall: Not using optimistic updates for drag-and-drop

How to avoid: Immediately update local state on drop, then fire the Server Action in the background. If the action fails, rollback to the snapshot state.

Pitfall: Using floating-point numbers for position ordering

How to avoid: Use integer positions with gaps (10, 20, 30). When inserting between 10 and 20, use 15. Periodically renormalize positions with a batch update.

Pitfall: Not scoping RLS to project membership

How to avoid: Create RLS policies that check project_members: SELECT on tasks requires the user to be a member of the task's column's project.

Best practices

  • Use @dnd-kit for drag-and-drop — it is the most flexible React DnD library and works well with shadcn/ui Card components
  • Implement optimistic drag-and-drop by updating local state immediately and persisting via Server Action in the background
  • Use RLS policies joined through project_members to ensure users only access their own projects' tasks
  • Use Design Mode (Option+D) to visually adjust Card sizing, column widths, and priority Badge colors at zero credit cost
  • Use integer positions with gaps (10, 20, 30) for ordering to make reordering efficient without recalculating all positions
  • Add Tabs for board vs list view to accommodate different user preferences — some people prefer tables over kanban
  • Use connection pooling via Supavisor for concurrent board updates when multiple team members drag tasks simultaneously

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a kanban task management app with Next.js App Router, Supabase, and @dnd-kit. I need: 1) Draggable task cards between columns with optimistic updates, 2) Position-based ordering for cards within columns, 3) Task detail editor with assignee, priority, and due date, 4) RLS policies scoped to project membership. Help me design the schema and drag-and-drop state management.

Build Prompt

Implement optimistic drag-and-drop for a kanban board using @dnd-kit and React state. On dragEnd: 1) Save a snapshot of the current columns state, 2) Immediately move the task card to the target column in local state, 3) Call a Server Action to persist the column_id and position change, 4) If the Server Action throws an error, restore the snapshot state. Handle edge cases: dropping on the same column (reorder only), dropping on an empty column, and cancelling the drag.

Frequently asked questions

What drag-and-drop library does this use?

@dnd-kit is a lightweight, flexible React drag-and-drop library. Install it by prompting V0: 'add @dnd-kit/core and @dnd-kit/sortable'. It handles both cross-column dragging and within-column reordering.

How does the position ordering work?

Each task has an integer position column. Tasks within a column are ordered by position ascending. When a task is dropped, its position is updated to reflect the new order. Using gaps (10, 20, 30) allows inserting between positions without recalculating all positions.

What V0 plan do I need?

V0 Free tier works. The kanban board uses standard React components with @dnd-kit, shadcn/ui Cards, and Supabase. Design Mode for visual polish is also free.

Can multiple team members use the board simultaneously?

Yes. Each user's drag-and-drop updates persist independently via Server Actions. For real-time sync, add Supabase Realtime subscriptions on the tasks table so other users see board changes instantly.

How do I add more columns to the board?

Insert a new row into the columns table with the project_id and a position value. The board component queries columns ordered by position, so new columns appear automatically.

Can RapidDev help build a custom project management tool?

Yes. RapidDev has built 600+ apps including project management platforms with Gantt charts, resource allocation, and sprint planning. Book a free consultation to discuss your workflow requirements.

How do I deploy this to production?

Click Share > Publish in V0. The Supabase connection is auto-configured from the Connect panel. The kanban board works identically in production — drag-and-drop, real-time updates, and all.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.