# How to Build Project management tool with V0

- Tool: v0
- Difficulty: Intermediate
- Compatibility: V0 Premium or higher
- Last updated: April 2026

## TL;DR

Build a Trello-style project management tool with V0 using Next.js, Supabase, and drag-and-drop kanban boards. Features team collaboration with role-based access, real-time card updates via Supabase Realtime, comments, and label management — all in about 1-2 hours from your browser.

## Before you start

- A V0 account (Premium recommended for prompt queuing)
- A Supabase project (free tier works — connect via V0's Connect panel)
- A team or project to organize (even a personal project works for testing)
- Basic understanding of kanban workflow (columns for stages, cards for tasks)

## Step-by-step guide

### 1. Set up the project and database schema with Supabase

Open V0 and create a new project. Use the Connect panel to add Supabase. Prompt V0 to create the full schema for projects, boards, cards, members, and comments. Enable Realtime on cards and comments tables.

```
// Paste this prompt into V0's AI chat:
// Build a project management tool. Create a Supabase schema with:
// 1. projects: id (uuid PK), owner_id (uuid FK), name (text), description (text), created_at (timestamptz)
// 2. boards: id (uuid PK), project_id (uuid FK), name (text), position (integer), created_at (timestamptz)
// 3. cards: id (uuid PK), board_id (uuid FK), title (text), description (text), assignee_id (uuid FK nullable), labels (text[]), due_date (date), position (integer), created_at (timestamptz), updated_at (timestamptz)
// 4. members: id (uuid PK), project_id (uuid FK), user_id (uuid FK), role (text check owner/admin/member/viewer), invited_at (timestamptz) with unique constraint on (project_id, user_id)
// 5. comments: id (uuid PK), card_id (uuid FK), author_id (uuid FK), content (text), created_at (timestamptz)
// Enable Supabase Realtime on cards and comments tables.
// RLS: cards and comments require membership in the project. Members can be read by other project members.
// Generate SQL migration and TypeScript types.
```

> Pro tip: Use V0's prompt queuing — queue the schema prompt first, then immediately queue the board view and card detail prompts while the schema generates.

**Expected result:** Supabase is connected with all five tables created. Realtime is enabled on cards and comments. RLS policies enforce project-level access control.

### 2. Build the kanban board view with drag-and-drop

Create the main board page showing columns (boards) with draggable cards. Use @dnd-kit/core for smooth drag-and-drop. When a card is dropped on a different column, update its board_id and position optimistically.

```
// Paste this prompt into V0's AI chat:
// Build a kanban board view at app/projects/[id]/page.tsx.
// Requirements:
// - Fetch project boards and cards from Supabase
// - Display boards as columns in a horizontal ScrollArea
// - Each column has: board name header, list of Cards, "Add Card" Button at bottom
// - Each Card shows: title, assignee Avatar, labels as Badges, due date text
// - Use @dnd-kit/core for drag-and-drop:
//   - DndContext wrapping all columns
//   - SortableContext for each column
//   - useSortable on each Card
//   - onDragEnd handler that calls moveCard Server Action with new board_id and position
// - Clicking a Card opens a Sheet (right sidebar) with card details
// - "Add Board" Button to create new columns via Dialog
// - Use optimistic updates: useOptimistic to move the card visually before Server Action completes
// - 'use client' for the board component (drag-and-drop requires browser APIs)
// - Server Actions: createCard(), moveCard(), createBoard()
```

**Expected result:** A horizontal kanban board with draggable cards across columns. Cards move visually on drop with optimistic updates. Clicking a card opens a detail Sheet on the right.

### 3. Create the card detail sidebar with comments

Build the Sheet component that opens when clicking a card. It shows the full card details — description, assignee, labels, due date — and a comment thread at the bottom.

```
// Paste this prompt into V0's AI chat:
// Build a card detail Sheet component for the project management board.
// Requirements:
// - Opens from right side when clicking a card on the kanban board
// - Header: card title (editable Input on click), DropdownMenu for actions (delete, archive)
// - Body sections:
//   - Description: Textarea, auto-saves on blur via Server Action
//   - Assignee: Avatar + name, Select dropdown to change assignee from project members
//   - Labels: Badge display, Popover with ToggleGroup to add/remove labels (bug/feature/design/urgent/documentation)
//   - Due Date: Calendar + Popover (DatePicker pattern)
//   - Separator between sections
// - Comments section at bottom:
//   - List of existing comments with Avatar, author name, timestamp, content
//   - Textarea + Button for adding new comment via Server Action addComment()
//   - New comments appear instantly via optimistic update
// - Server Actions: updateCard(), deleteCard(), addComment()
// - 'use client' component for interactivity
```

**Expected result:** A right-side Sheet showing card details with editable fields for description, assignee, labels, and due date. The comments section shows a threaded conversation with add-comment functionality.

### 4. Add real-time collaboration with Supabase Realtime

Subscribe to changes on the cards and comments tables so team members see updates instantly without refreshing. When another user moves a card or adds a comment, the board updates in real time.

```
'use client'

import { useEffect } from 'react'
import { createClient } from '@/lib/supabase/client'
import { useRouter } from 'next/navigation'

export function RealtimeProvider({
  projectId,
  children,
}: {
  projectId: string
  children: React.ReactNode
}) {
  const router = useRouter()
  const supabase = createClient()

  useEffect(() => {
    const channel = supabase
      .channel(`project-${projectId}`)
      .on(
        'postgres_changes',
        {
          event: '*',
          schema: 'public',
          table: 'cards',
          filter: `board_id=in.(select id from boards where project_id='${projectId}')`,
        },
        () => router.refresh()
      )
      .on(
        'postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'comments',
        },
        () => router.refresh()
      )
      .subscribe()

    return () => {
      supabase.removeChannel(channel)
    }
  }, [projectId, router, supabase])

  return <>{children}</>
}
```

> Pro tip: Wrap the board page with this RealtimeProvider component. When any team member makes a change, router.refresh() fetches fresh data from Supabase and updates the Server Components.

**Expected result:** When team member A moves a card, team member B sees the card move automatically without refreshing. New comments also appear in real time.

### 5. Build the member management and project settings page

Create a settings page where project owners can invite team members, assign roles, and manage project details. Use the Supabase members table with role-based access control.

```
// Paste this prompt into V0's AI chat:
// Build project settings at app/projects/[id]/settings/page.tsx.
// Requirements:
// - Only accessible to project owner and admins
// - Two sections via shadcn/ui Tabs: "General" and "Members"
// - General tab: Input for project name, Textarea for description, save Button via Server Action
// - Members tab:
//   - Table of current members with columns: Avatar + name, email, role Badge, joined date
//   - Role column has a Select dropdown (owner/admin/member/viewer) for role changes (only owner can change roles)
//   - DropdownMenu on each row for actions: Change Role, Remove Member
//   - "Invite Member" Button opens Dialog with email Input and role Select
//   - Server Actions: inviteMember(), updateMemberRole(), removeMember()
// - AlertDialog for confirming member removal
// - Toast notification for successful invite
```

**Expected result:** A settings page with General and Members tabs. The Members tab shows a Table of team members with role Badges and a Dialog for inviting new members by email.

## Complete code example

File: `app/actions/cards.ts`

```typescript
'use server'

import { createClient } from '@/lib/supabase/server'
import { auth } from '@clerk/nextjs/server'
import { revalidatePath } from 'next/cache'

export async function createCard(
  boardId: string,
  title: string,
  projectId: string
) {
  const supabase = await createClient()

  const { data: maxPos } = await supabase
    .from('cards')
    .select('position')
    .eq('board_id', boardId)
    .order('position', { ascending: false })
    .limit(1)
    .single()

  await supabase.from('cards').insert({
    board_id: boardId,
    title,
    position: (maxPos?.position ?? 0) + 1,
  })

  revalidatePath(`/projects/${projectId}`)
}

export async function moveCard(
  cardId: string,
  newBoardId: string,
  newPosition: number,
  projectId: string
) {
  const supabase = await createClient()

  await supabase
    .from('cards')
    .update({
      board_id: newBoardId,
      position: newPosition,
      updated_at: new Date().toISOString(),
    })
    .eq('id', cardId)

  revalidatePath(`/projects/${projectId}`)
}

export async function addComment(
  cardId: string,
  content: string,
  projectId: string
) {
  const { userId } = await auth()
  const supabase = await createClient()

  await supabase.from('comments').insert({
    card_id: cardId,
    author_id: userId,
    content,
  })

  revalidatePath(`/projects/${projectId}`)
}
```

## Common mistakes

- **Not using optimistic updates for drag-and-drop card movements** — Waiting for the Server Action round-trip before moving the card visually makes drag-and-drop feel broken with a visible delay. Fix: Use React's useOptimistic hook to immediately update the card's position in local state, then persist via Server Action in the background.
- **Implementing drag-and-drop in a Server Component** — Drag-and-drop requires browser event handlers (onDragStart, onDrop) and state management, which Server Components cannot handle. Fix: Mark the kanban board component with 'use client'. Keep the data fetching in a Server Component parent and pass cards as props to the client board.
- **Not handling position reordering in a database transaction** — When moving a card to a new position, other cards need their positions shifted. Without a transaction, concurrent moves can result in duplicate positions. Fix: Use a Supabase RPC function that wraps the position update in a transaction — update the moved card's board_id and position, then shift sibling cards atomically.

## Best practices

- Use V0's prompt queuing to generate the board view, card detail Sheet, and member management in rapid succession
- Enable Supabase Realtime on cards and comments tables for instant team collaboration
- Use @dnd-kit/core for accessible, performant drag-and-drop rather than the HTML5 drag-and-drop API
- Implement RLS policies scoped to project membership so cards are only visible to team members
- Use V0's Design Mode (Option+D) to visually adjust card sizes, column widths, and badge colors for free
- Keep the RealtimeProvider as a wrapper component that calls router.refresh() to avoid manually managing subscription state
- Store card labels as a text[] array for simplicity — no need for a separate labels table in the MVP

## Frequently asked questions

### What V0 plan do I need for a project management tool?

V0 Free works for the basic build, but Premium ($20/month) is recommended because the project has multiple complex pages (board view, card details, settings) that benefit from prompt queuing.

### How does real-time collaboration work?

Supabase Realtime listens for database changes on the cards and comments tables. When any team member makes a change, the RealtimeProvider component triggers a router.refresh() that fetches fresh data and updates the board for all connected users.

### Can I limit what different team members can do?

Yes. The members table has a role field (owner/admin/member/viewer). RLS policies on the database level and role checks in Server Actions enforce what each role can do — viewers can only read, members can create and edit, admins can manage members.

### How do I deploy the project management tool?

Click Share then Publish to Production in V0. Alternatively, use the Git panel to connect to GitHub and auto-create a PR for team code review before deploying to Vercel.

### Can I import projects from Trello or Asana?

Not out of the box. You would need to build an import API route that accepts the exported JSON from Trello/Asana and maps it to your Supabase schema. Both tools offer public APIs for data export.

### Can RapidDev help build a custom project management platform?

Yes. RapidDev has built 600+ apps including team collaboration platforms with kanban boards, Gantt charts, and custom workflow engines. Book a free consultation to discuss your specific requirements.

---

Source: https://www.rapidevelopers.com/how-to-build-v0/project-management-tool
© RapidDev — https://www.rapidevelopers.com/how-to-build-v0/project-management-tool
