Self-contained drag-and-drop board. Import `<KanbanBoard />` and pass `columns` + a `cards` record keyed by column id. The same component powers two layouts: a status-grouped **Board view** and a **Manage My Queue** board with a capped Active column. The board manages drag state internally and reports the next mapping via `onChange`.
Queued
2projects
Stewardship campaign brief
Queued
AK
Volunteer recruitment poster
Queued
DP
In progress
2projects
Easter sermon series — graphics
In progress
JB
Mid-week graphics — week 7
In progress
SR
Concept review
1project
Outreach video — first pass
Concept review
CL
Delivered
1project
Annual report cover
Delivered
RS
Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/kanban-board-showcase.jsonUsage
Declare your columns + a cards record keyed by column id, then render the board.
TSImport
import {
KanbanBoard,
type KanbanBoardProps,
type KanbanColumn,
type KanbanCard,
type KanbanStatus,
type KanbanTone,
} from "@/components/blocks/kanban-board-showcase";TSExample
"use client";
import * as React from "react";
import {
KanbanBoard,
type KanbanCard,
type KanbanColumn,
} from "@/components/blocks/kanban-board-showcase";
const COLUMNS: KanbanColumn[] = [
{ id: "queued", title: "Queued", tone: "muted", showHandle: true },
{ id: "active", title: "Active", tone: "emerald", cap: 7, disableDnd: true, showPlayPause: true },
];
const INITIAL_CARDS: Record<string, KanbanCard[]> = {
queued: [
{ id: "c1", title: "Stewardship brief", status: { label: "Open", tone: "muted" } },
{ id: "c2", title: "Outreach video", status: { label: "Open", tone: "muted" } },
],
active: [
{ id: "c5", title: "Easter graphics", status: { label: "Active", tone: "brand" } },
{ id: "c7", title: "Annual report cover", status: { label: "Active", tone: "brand" } },
],
};
export function Example() {
const [cards, setCards] = React.useState(INITIAL_CARDS);
return <KanbanBoard columns={COLUMNS} cards={cards} onChange={setCards} />;
}Composition
Anatomy of the kanban board.
KanbanBoard
├── KanbanColumn[] (props.columns — id, title, tone, cap, showHandle, disableDnd, showPlayPause)
└── Cards record (props.cards — Record<columnId, KanbanCard[]>)
└── KanbanCard
├── title / description
├── status: { label, tone }
├── assignee: { name, initials }
└── paused (only meaningful in showPlayPause columns — flips Pause ↔ Play)
Drag rules
├── Reordering is allowed within a column.
└── Cross-column moves are blocked — cards never leave their source column.Board view
One column per project status (Queued / In progress / Concept review / Delivered), cards grouped by stage with a status badge + owner avatar — the same layout as the project board. Drag-and-drop reorders within a column; cross-column moves are blocked.
Queued
2projects
Stewardship campaign brief
Queued
AK
Volunteer recruitment poster
Queued
DP
In progress
2projects
Easter sermon series — graphics
In progress
JB
Mid-week graphics — week 7
In progress
SR
Concept review
1project
Outreach video — first pass
Concept review
CL
Delivered
1project
Annual report cover
Delivered
RS
Manage My Queue
A Queued column (drag handles + per-card position marker, drag to reorder) beside a locked Active column (`disableDnd: true`) capped at 7 so the count pill grades green / yellow / red, with `showPlayPause: true` rendering a per-card Play/Pause toggle.
Queued Projects
5projects
#1Stewardship campaign brief
Open
AK
#2Outreach video — first pass
Open
CL
#3Hospitality refresh icons
Open
RS
#4Volunteer recruitment poster
Open
DP
#5Stewardship campaign — Q3 follow-up email sequence with merge tags for first name + giving tier
Open
SR
Active Projects
3 / 7projects
Easter sermon series — graphics
Active
JB
Mid-week graphics — week 7
Active
SR
Annual report cover
Active
AK
API Reference
Props for the board, its columns, and individual cards.
KanbanBoard
| Prop | Type | Default | Description |
|---|---|---|---|
columns | KanbanColumn[] | — | Column configuration. |
cards | Record<string, KanbanCard[]> | — | Cards keyed by column id. |
onChange | (next: Record<string, KanbanCard[]>) => void | — | Fired with the new mapping after every drag. |
KanbanColumn
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | — | Matches a key in the cards record. |
title | string | — | Header label. |
tone | "brand" | "emerald" | "amber" | "sky" | "muted" | "fuchsia" | "rose" | — | Color of the header dot. |
cap | number | — | Soft cap; the count pill grades green/yellow/red against it. |
showHandle | boolean | false | Visible drag handle + position marker on each card. |
disableDnd | boolean | false | Lock the column — no drags out, no drops in. |
showPlayPause | boolean | false | Render a small solid Play/Pause toggle on the left of every card. Cards flip between Pause and Play based on `card.paused`. |
KanbanCard
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | — | Stable card identifier. |
title | string | — | Card title. |
description | string | — | Optional secondary text. |
status | { label: string; tone?: KanbanTone } | — | Status badge. |
assignee | { name: string; initials: string } | — | Avatar slot. |
paused | boolean | false | Only meaningful in `showPlayPause` columns. Controls which icon renders (Play when true, Pause otherwise). Toggled internally on click. |