cult/ui

Kanban Board

Cards 61

About

A drag-and-drop kanban board column component for organizing cards into statuses like to-do, in-progress, and done.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Kanban Board to your project and fits it to your theme.

Code

How Kanban Board is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import {
  Column,
  gentleSpring,
  type ColumnData,
  type ColumnStatus,
  type DragState,
} from "@/components/ui/kanban-board";

import { useCallback, useState } from "react";

import { MotionConfig } from "motion/react";

const SAMPLE_COLUMNS: ColumnData[] = [
  {
    id: "todo",
    title: "Backlog",
    status: "todo",
    cards: [
      {
        id: "1",
        title: "Draft Q2 nurture email series",
        tags: [{ label: "Campaign", variant: "feature" }],
        assignee: {
          name: "Méschac Irung",
          avatar: "https://cdn.21st.dev/assets/mirror/ac/ac9c53a00de9dbc0d8733c544cb6f33f58bfe72943ec1c90f235716887ade7fa.jpg",
        },
        date: "Jan 8",
      },
      {
        id: "2",
        title: "Fix broken UTMs on paid search ads",
        tags: [{ label: "Tracking", variant: "bug" }],
        assignee: {
          name: "Bernard Ngandu",
          avatar: "https://cdn.21st.dev/assets/mirror/51/511e8a28ad384d1692e249771c5da88414ce778aec74ea21451d519db92e0038.jpg",
        },
        date: "Jan 5",
      },
      {
        id: "6",
        title: "Refresh one-pager for enterprise pitch",
        tags: [{ label: "Collateral", variant: "docs" }],
        assignee: { name: "Anika Patel" },
        date: "Jan 9",
      },
    ],
  },
  {
    id: "in-progress",
    title: "In flight",
    status: "in-progress",
    cards: [
      {
        id: "3",
        title: "Homepage hero & social proof block",
        tags: [{ label: "Creative", variant: "design" }],
        assignee: {
          name: "Théo Balick",
          avatar: "https://cdn.21st.dev/assets/mirror/6b/6bfe57db510ea4a7faa6b39f90b7ec15658233f079d0bac3ef425859f211b0b2.jpg",
        },
        date: "Jan 6",
      },
      {
        id: "7",
        title: "A/B test LinkedIn lead-gen forms",
        tags: [{ label: "Growth", variant: "improvement" }],
        assignee: {
          name: "Liam Chen",
          avatar: "https://cdn.21st.dev/assets/mirror/2b/2b7b08655f93dbb0d950da430b1e251dcfede184e47d81a2c425514cd529b0fe.jpg",
        },
        date: "Jan 7",
      },
    ],
  },
  {
    id: "done",
    title: "Launched",
    status: "done",
    cards: [
      {
        id: "4",
        title: "Spring launch landing page live",
        tags: [{ label: "Launch", variant: "done" }],
        assignee: {
          name: "Méschac Irung",
          avatar: "https://cdn.21st.dev/assets/mirror/ac/ac9c53a00de9dbc0d8733c544cb6f33f58bfe72943ec1c90f235716887ade7fa.jpg",
        },
        date: "Jan 3",
      },
    ],
  },
];

function KanbanBoardDemo() {
  const [columns, setColumns] = useState<ColumnData[]>(SAMPLE_COLUMNS);

  const [dragState, setDragState] = useState<DragState>(null);

  const handleDrop = useCallback(
    (cardId: string, fromColumnId: ColumnStatus, toColumnId: ColumnStatus) => {
      if (fromColumnId === toColumnId) {
        return;
      }
      setColumns((prev) => {
        const fromCol = prev.find((c) => c.id === fromColumnId);
        const card = fromCol?.cards.find((c) => c.id === cardId);
        if (!card) {
          return prev;
        }
        return prev.map((col) => {
          if (col.id === fromColumnId) {
            return {
              ...col,
              cards: col.cards.filter((c) => c.id !== cardId),
            };
          }
          if (col.id === toColumnId) {
            return { ...col, cards: [...col.cards, card] };
          }
          return col;
        });
      });
    },
    [],
  );

  return (
    <MotionConfig transition={gentleSpring}>
      <div
        className="flex min-h-[500px] w-full max-w-4xl items-start justify-center bg-[#f0f0f0]/20 px-6 py-12 antialiased dark:bg-transparent"
        style={{
          fontFamily:
            '-apple-system, BlinkMacSystemFont, "SF Pro Display", system-ui, sans-serif',
          WebkitFontSmoothing: "antialiased",
        }}
      >
        <div className="w-full max-w-[520px]">
          <div className="grid grid-cols-3 gap-5">
            {columns.map((col) => (
              <Column
                column={col}
                dragState={dragState}
                key={col.id}
                onDrop={handleDrop}
                setDragState={setDragState}
              />
            ))}
          </div>
        </div>
      </div>
    </MotionConfig>
  );
}

export default KanbanBoardDemo;

Dependencies

More by cult/ui

View all
  • 3D Carousel

    3D Carousel

    2.5K
  • Logo Carousel

    Logo Carousel

    1.1K
  • Feature Carousel

    Feature Carousel

    504
  • Popover Form

    Popover Form

    419
  • Floating Panel

    Floating Panel

    245
  • Expandable

    Expandable

    126
  • Dock

    Dock

    119
  • Hero Liquid Metal

    Hero Liquid Metal

    101

More in Cards

View all
  • Stats cards with links

    Stats cards with links

    33
  • Course Design Cards

    Course Design Cards

    1.1K
  • Card

    Card

    70
  • Statistics Card 2

    Statistics Card 2

    264
  • Statistics Card 10

    Statistics Card 10

    263
  • Card

    Card

    17
  • Card Studio

    Card Studio

    9
  • The Item one

    The Item one

    3