Docs Components Advanced

Kanban

React example follows below.
ts
/**
* Kanban's pinned demo state (CSP-D3).
*
* Four columns so the board fills its `md:grid-cols-4` root, and the card
* distribution is deliberately uneven — two in the first column, one in each
* middle column, one at the end — so the demo renders a card *mid-column*
* rather than only at the ends, and every column header shows a different
* live count. Both `priority` tags and both assignee-chip states (with and
* without an explicit chip colour) appear at least once.
*
* `rail` / `assigneeColor` carry raw token strings under names the fixture
* proof allows; a bare `color` key would be rejected.
*/
const kanbanFixture = {
id: "kanban",
columns: [
{ id: "backlog", name: "Backlog", rail: "var(--text-muted)" },
{ id: "progress", name: "In progress", rail: "var(--accent)" },
{ id: "review", name: "In review", rail: "var(--vs-info)" },
{ id: "done", name: "Done", rail: "var(--vs-success)" },
],
cards: [
{
id: "card-rotate-keys",
columnId: "backlog",
title: "Rotate signing keys",
priority: "high",
assignee: "AM",
assigneeColor: "var(--accent)",
},
{
id: "card-retry-budget",
columnId: "backlog",
title: "Per-tenant retry budget",
priority: "low",
assignee: "RS",
},
{
id: "card-idempotency",
columnId: "progress",
title: "Idempotency keys",
priority: "high",
assignee: "JL",
assigneeColor: "var(--vs-info)",
},
{
id: "card-webhook-replay",
columnId: "review",
title: "Webhook replay window",
priority: "med",
assignee: "PK",
assigneeColor: "var(--vs-warning)",
},
{
id: "card-ledger-cutover",
columnId: "done",
title: "Ledger cutover runbook",
priority: "med",
assignee: "TD",
assigneeColor: "var(--vs-success)",
},
],
} satisfies {
id: string;
columns: { id: string; name: string; rail: string }[];
cards: {
id: string;
columnId: string;
title: string;
priority: "low" | "med" | "high";
assignee: string;
assigneeColor?: string;
}[];
};
import { Kanban } from "@grassroot/ui-react/advanced";
export default function KanbanDemo() {
return (
<div>
<Kanban columns={kanbanFixture.columns} defaultCards={kanbanFixture.cards} />
</div>
);
}
ts
<script setup lang="ts">
/**
* Kanban's pinned demo state (CSP-D3).
*
* Four columns so the board fills its `md:grid-cols-4` root, and the card
* distribution is deliberately uneven — two in the first column, one in each
* middle column, one at the end — so the demo renders a card *mid-column*
* rather than only at the ends, and every column header shows a different
* live count. Both `priority` tags and both assignee-chip states (with and
* without an explicit chip colour) appear at least once.
*
* `rail` / `assigneeColor` carry raw token strings under names the fixture
* proof allows; a bare `color` key would be rejected.
*/
const kanbanFixture = {
id: "kanban",
columns: [
{ id: "backlog", name: "Backlog", rail: "var(--text-muted)" },
{ id: "progress", name: "In progress", rail: "var(--accent)" },
{ id: "review", name: "In review", rail: "var(--vs-info)" },
{ id: "done", name: "Done", rail: "var(--vs-success)" },
],
cards: [
{
id: "card-rotate-keys",
columnId: "backlog",
title: "Rotate signing keys",
priority: "high",
assignee: "AM",
assigneeColor: "var(--accent)",
},
{
id: "card-retry-budget",
columnId: "backlog",
title: "Per-tenant retry budget",
priority: "low",
assignee: "RS",
},
{
id: "card-idempotency",
columnId: "progress",
title: "Idempotency keys",
priority: "high",
assignee: "JL",
assigneeColor: "var(--vs-info)",
},
{
id: "card-webhook-replay",
columnId: "review",
title: "Webhook replay window",
priority: "med",
assignee: "PK",
assigneeColor: "var(--vs-warning)",
},
{
id: "card-ledger-cutover",
columnId: "done",
title: "Ledger cutover runbook",
priority: "med",
assignee: "TD",
assigneeColor: "var(--vs-success)",
},
],
} satisfies {
id: string;
columns: { id: string; name: string; rail: string }[];
cards: {
id: string;
columnId: string;
title: string;
priority: "low" | "med" | "high";
assignee: string;
assigneeColor?: string;
}[];
};
import { Kanban } from "@grassroot/ui-vue";
</script>
<template>
<div>
<Kanban :columns="kanbanFixture.columns" :default-cards="kanbanFixture.cards" />
</div>
</template>
ts
import { Component } from "@angular/core";
/**
* Kanban's pinned demo state (CSP-D3).
*
* Four columns so the board fills its `md:grid-cols-4` root, and the card
* distribution is deliberately uneven — two in the first column, one in each
* middle column, one at the end — so the demo renders a card *mid-column*
* rather than only at the ends, and every column header shows a different
* live count. Both `priority` tags and both assignee-chip states (with and
* without an explicit chip colour) appear at least once.
*
* `rail` / `assigneeColor` carry raw token strings under names the fixture
* proof allows; a bare `color` key would be rejected.
*/
const kanbanFixture = {
id: "kanban",
columns: [
{ id: "backlog", name: "Backlog", rail: "var(--text-muted)" },
{ id: "progress", name: "In progress", rail: "var(--accent)" },
{ id: "review", name: "In review", rail: "var(--vs-info)" },
{ id: "done", name: "Done", rail: "var(--vs-success)" },
],
cards: [
{
id: "card-rotate-keys",
columnId: "backlog",
title: "Rotate signing keys",
priority: "high",
assignee: "AM",
assigneeColor: "var(--accent)",
},
{
id: "card-retry-budget",
columnId: "backlog",
title: "Per-tenant retry budget",
priority: "low",
assignee: "RS",
},
{
id: "card-idempotency",
columnId: "progress",
title: "Idempotency keys",
priority: "high",
assignee: "JL",
assigneeColor: "var(--vs-info)",
},
{
id: "card-webhook-replay",
columnId: "review",
title: "Webhook replay window",
priority: "med",
assignee: "PK",
assigneeColor: "var(--vs-warning)",
},
{
id: "card-ledger-cutover",
columnId: "done",
title: "Ledger cutover runbook",
priority: "med",
assignee: "TD",
assigneeColor: "var(--vs-success)",
},
],
} satisfies {
id: string;
columns: { id: string; name: string; rail: string }[];
cards: {
id: string;
columnId: string;
title: string;
priority: "low" | "med" | "high";
assignee: string;
assigneeColor?: string;
}[];
};
import { GrassrootKanban } from "@grassroot/ui-angular";
// Every input is a `[property]` binding, never a static attribute — Angular
// leaves static template attributes on the host element in the rendered DOM,
// where the other four frameworks have nothing.
@Component({
selector: "app-kanban-demo",
standalone: true,
imports: [GrassrootKanban],
template: `
<div>
<grassroot-kanban [columns]="columns" [defaultCards]="cards" />
</div>
`,
})
export class KanbanDemoComponent {
protected readonly columns = kanbanFixture.columns;
protected readonly cards = kanbanFixture.cards;
}
export default KanbanDemoComponent;
ts
<script lang="ts">
/**
* Kanban's pinned demo state (CSP-D3).
*
* Four columns so the board fills its `md:grid-cols-4` root, and the card
* distribution is deliberately uneven — two in the first column, one in each
* middle column, one at the end — so the demo renders a card *mid-column*
* rather than only at the ends, and every column header shows a different
* live count. Both `priority` tags and both assignee-chip states (with and
* without an explicit chip colour) appear at least once.
*
* `rail` / `assigneeColor` carry raw token strings under names the fixture
* proof allows; a bare `color` key would be rejected.
*/
const kanbanFixture = {
id: "kanban",
columns: [
{ id: "backlog", name: "Backlog", rail: "var(--text-muted)" },
{ id: "progress", name: "In progress", rail: "var(--accent)" },
{ id: "review", name: "In review", rail: "var(--vs-info)" },
{ id: "done", name: "Done", rail: "var(--vs-success)" },
],
cards: [
{
id: "card-rotate-keys",
columnId: "backlog",
title: "Rotate signing keys",
priority: "high",
assignee: "AM",
assigneeColor: "var(--accent)",
},
{
id: "card-retry-budget",
columnId: "backlog",
title: "Per-tenant retry budget",
priority: "low",
assignee: "RS",
},
{
id: "card-idempotency",
columnId: "progress",
title: "Idempotency keys",
priority: "high",
assignee: "JL",
assigneeColor: "var(--vs-info)",
},
{
id: "card-webhook-replay",
columnId: "review",
title: "Webhook replay window",
priority: "med",
assignee: "PK",
assigneeColor: "var(--vs-warning)",
},
{
id: "card-ledger-cutover",
columnId: "done",
title: "Ledger cutover runbook",
priority: "med",
assignee: "TD",
assigneeColor: "var(--vs-success)",
},
],
} satisfies {
id: string;
columns: { id: string; name: string; rail: string }[];
cards: {
id: string;
columnId: string;
title: string;
priority: "low" | "med" | "high";
assignee: string;
assigneeColor?: string;
}[];
};
import { Kanban } from "@grassroot/ui-svelte";
</script>
<div>
<Kanban columns={kanbanFixture.columns} defaultCards={kanbanFixture.cards} />
</div>
ts
/** @jsxImportSource solid-js */
/**
* Kanban's pinned demo state (CSP-D3).
*
* Four columns so the board fills its `md:grid-cols-4` root, and the card
* distribution is deliberately uneven — two in the first column, one in each
* middle column, one at the end — so the demo renders a card *mid-column*
* rather than only at the ends, and every column header shows a different
* live count. Both `priority` tags and both assignee-chip states (with and
* without an explicit chip colour) appear at least once.
*
* `rail` / `assigneeColor` carry raw token strings under names the fixture
* proof allows; a bare `color` key would be rejected.
*/
const kanbanFixture = {
id: "kanban",
columns: [
{ id: "backlog", name: "Backlog", rail: "var(--text-muted)" },
{ id: "progress", name: "In progress", rail: "var(--accent)" },
{ id: "review", name: "In review", rail: "var(--vs-info)" },
{ id: "done", name: "Done", rail: "var(--vs-success)" },
],
cards: [
{
id: "card-rotate-keys",
columnId: "backlog",
title: "Rotate signing keys",
priority: "high",
assignee: "AM",
assigneeColor: "var(--accent)",
},
{
id: "card-retry-budget",
columnId: "backlog",
title: "Per-tenant retry budget",
priority: "low",
assignee: "RS",
},
{
id: "card-idempotency",
columnId: "progress",
title: "Idempotency keys",
priority: "high",
assignee: "JL",
assigneeColor: "var(--vs-info)",
},
{
id: "card-webhook-replay",
columnId: "review",
title: "Webhook replay window",
priority: "med",
assignee: "PK",
assigneeColor: "var(--vs-warning)",
},
{
id: "card-ledger-cutover",
columnId: "done",
title: "Ledger cutover runbook",
priority: "med",
assignee: "TD",
assigneeColor: "var(--vs-success)",
},
],
} satisfies {
id: string;
columns: { id: string; name: string; rail: string }[];
cards: {
id: string;
columnId: string;
title: string;
priority: "low" | "med" | "high";
assignee: string;
assigneeColor?: string;
}[];
};
import { Kanban } from "@grassroot/ui-solid";
export default function KanbanDemo() {
return (
<div>
<Kanban columns={kanbanFixture.columns} defaultCards={kanbanFixture.cards} />
</div>
);
}

Installation

API Reference

Import

import { Kanban } from "@grassroot/ui-react/advanced";

Props

Prop
Type
Default
Requirement
columns
CoreKanbanColumn[]
required
cards
CoreKanbanCard[] | undefined
optional
defaultCards
CoreKanbanCard[] | undefined
optional
onMove
((cardId: string, toColumnId: string) => void) | undefined
optional
disableDrag
boolean | undefined
optional
Prop
columns
Type
CoreKanbanColumn[]
Default
Requirement
required
Prop
cards
Type
CoreKanbanCard[] | undefined
Default
Requirement
optional
Prop
defaultCards
Type
CoreKanbanCard[] | undefined
Default
Requirement
optional
Prop
onMove
Type
((cardId: string, toColumnId: string) => void) | undefined
Default
Requirement
optional
Prop
disableDrag
Type
boolean | undefined
Default
Requirement
optional

Events

EventPayload
onMove((cardId: string, toColumnId: string) => void) | undefined

Native attributes

Standard DOM and ARIA attributes not listed above spread onto the root element.

Import

import { Kanban } from "@grassroot/ui-vue";

Props

Prop
Type
Default
Requirement
columns
KanbanColumn[]
required
cards
KanbanCard[] | undefined
optional
defaultCards
KanbanCard[] | undefined
() => []
optional
disableDrag
boolean | undefined
false
optional
Prop
columns
Type
KanbanColumn[]
Default
Requirement
required
Prop
cards
Type
KanbanCard[] | undefined
Default
Requirement
optional
Prop
defaultCards
Type
KanbanCard[] | undefined
Default
() => []
Requirement
optional
Prop
disableDrag
Type
boolean | undefined
Default
false
Requirement
optional

Events

EventPayload
move[cardId: string, toColumnId: string]

Import

import { Component } from "@angular/core";
import { GrassrootKanban } from "@grassroot/ui-angular";

Props

Prop
Type
Default
Requirement
columns
KanbanColumn[]
required
cards
KanbanCard[]
optional
defaultCards
KanbanCard[]
[]
optional
disableDrag
boolean
false
optional
Prop
columns
Type
KanbanColumn[]
Default
Requirement
required
Prop
cards
Type
KanbanCard[]
Default
Requirement
optional
Prop
defaultCards
Type
KanbanCard[]
Default
[]
Requirement
optional
Prop
disableDrag
Type
boolean
Default
false
Requirement
optional

Events

EventPayload
move{ cardId: string; toColumnId: string }

Import

import { Kanban } from "@grassroot/ui-svelte";

Props

Prop
Type
Default
Requirement
columns
KanbanColumn[]
required
cards
KanbanCard[]
optional
defaultCards
KanbanCard[]
optional
onMove
(cardId: string, toColumnId: string) => void
optional
disableDrag
boolean
false
optional
className
string
optional
Prop
columns
Type
KanbanColumn[]
Default
Requirement
required
Prop
cards
Type
KanbanCard[]
Default
Requirement
optional
Prop
defaultCards
Type
KanbanCard[]
Default
Requirement
optional
Prop
onMove
Type
(cardId: string, toColumnId: string) => void
Default
Requirement
optional
Prop
disableDrag
Type
boolean
Default
false
Requirement
optional
Prop
className
Type
string
Default
Requirement
optional

Events

EventPayload
onMove(cardId: string, toColumnId: string) => void

Import

import { Kanban } from "@grassroot/ui-solid";

Props

Prop
Type
Default
Requirement
columns
KanbanColumn[]
required
cards
KanbanCard[]
optional
defaultCards
KanbanCard[]
optional
onMove
(cardId: string, toColumnId: string) => void
optional
disableDrag
boolean
optional
className
string
optional
Prop
columns
Type
KanbanColumn[]
Default
Requirement
required
Prop
cards
Type
KanbanCard[]
Default
Requirement
optional
Prop
defaultCards
Type
KanbanCard[]
Default
Requirement
optional
Prop
onMove
Type
(cardId: string, toColumnId: string) => void
Default
Requirement
optional
Prop
disableDrag
Type
boolean
Default
Requirement
optional
Prop
className
Type
string
Default
Requirement
optional

Events

EventPayload
onMove(cardId: string, toColumnId: string) => void