<kanban-board>
Columnar drag-and-drop board with user-defined columns, count badges, and optional WIP limits.
Overview
A web component that renders a columnar drag-and-drop board with user-defined columns. Each column wraps a <drag-surface> for native drag-and-drop and keyboard-accessible reordering and transfer. Supports any draggable content — user-story cards, plain articles, or custom elements. Includes auto-updating count badges, optional WIP (work-in-progress) limits with visual warnings, and optional column color tinting.
<kanban-board> <section data-column="backlog" data-column-label="Backlog"> <user-story draggable="true" data-id="PROJ-201" story-id="PROJ-201" priority="high" status="to-do" points="5" detail="minimal"> <span slot="persona">Sarah Chen</span> <span slot="action">view all project timelines in one dashboard</span> </user-story> </section> <section data-column="in-progress" data-column-label="In Progress"> <user-story draggable="true" data-id="PROJ-198" story-id="PROJ-198" priority="high" status="in-progress" points="5" detail="minimal"> <span slot="persona">Jordan Park</span> <span slot="action">filter reports by date range and team</span> </user-story> </section> <section data-column="review" data-column-label="Review"> </section> <section data-column="done" data-column-label="Done" data-column-color="success"> </section></kanban-board>
Attributes
| Attribute | Type | Description |
|---|---|---|
src | string | URL to JSON data for columns and items |
title | string | Optional heading displayed above the board |
compact | boolean | Reduced spacing variant for dashboard-style layouts |
Child Attributes
Set these on <section> children to define columns:
| Attribute | On | Type | Description |
|---|---|---|---|
data-column | <section> | string | Column identifier (e.g. "backlog", "doing", "done") |
data-column-label | <section> | string | Display label for the column header. Falls back to title-cased data-column |
data-wip | <section> | number | Optional WIP limit. Visual warning when exceeded |
data-column-color | <section> | string | Color token for column tint: success, warning, error, info |
Set these on draggable children within sections:
| Attribute | Type | Description |
|---|---|---|
draggable="true" | boolean | Required for drag capability (auto-added if missing) |
data-id | string | Stable identifier for the item (auto-generated if missing) |
WIP Limits
Add data-wip to any column section to set a work-in-progress limit. When the number of items in a column exceeds the limit, the component adds a data-wip-exceeded attribute to the column and fires a kanban-board:wip-exceeded event. Items are not blocked from entering — this is a visual warning only.
<kanban-board> <section data-column="backlog" data-column-label="Backlog"> <article draggable="true" data-id="task-1">Task A</article> <article draggable="true" data-id="task-2">Task B</article> </section> <section data-column="doing" data-column-label="In Progress" data-wip="2"> <article draggable="true" data-id="task-3">Task C</article> <article draggable="true" data-id="task-4">Task D</article> </section> <section data-column="done" data-column-label="Done"> </section></kanban-board>
Generic Content
The board is not limited to sprint planning. Any draggable content works — blog posts through a publishing pipeline, support tickets, hiring candidates, or content workflows.
<kanban-board> <section data-column="ideas" data-column-label="Ideas" data-column-color="info"> <article draggable="true" data-id="post-1"> <strong>CSS Container Queries Deep Dive</strong> <small>Tutorial</small> </article> </section> <section data-column="drafting" data-column-label="Drafting" data-wip="2"> </section> <section data-column="published" data-column-label="Published" data-column-color="success"> </section></kanban-board>
Events
| Event | Detail | Description |
|---|---|---|
kanban-board:transfer | { itemId, fromColumn, toColumn, newIndex, item } | Item moved between columns |
kanban-board:reorder | { itemId, column, oldIndex, newIndex } | Item reordered within a column |
kanban-board:ready | { columnCount, itemCount } | Fired after component initializes |
kanban-board:wip-exceeded | { column, limit, count } | Fired when a column exceeds its WIP limit |
kanban-board:upgraded | — | Fired once after first connect; safe signal for assigning .items from a framework. |
kanban-board:items-changed | { items, source: 'api' \| 'drag' } | Fired after the items array changes for any reason. Filter on source to avoid feedback loops. |
Property API (JS-first)
Reactive frameworks (Montane, Lit, Solid, Vue, React, Svelte) can drive the board directly with property assignments — no need to template the column structure. Existing card nodes whose id is in both the previous and next list are preserved across diffs: in-flight drag state, focus, and CSS animations survive untouched.
| Property | Type | Description |
|---|---|---|
.columns | { id, label?, wip?, color? }[] | Replaces all columns and rebuilds the shell. Setting an empty array clears the board. |
.items | { id, column, ... }[] | Setter runs a keyed diff. id is the key; column selects the target drag-surface. Other fields go to the renderer. |
.renderItem | (item) => Element | Optional custom item renderer. Default builds a <work-item> and applies fields via .data. |
const board = document.querySelector('kanban-board'); // Wait for the upgrade signal before assigning.await new Promise(r => board.addEventListener('kanban-board:upgraded', r, { once: true })); board.columns = [ { id: 'todo', label: 'To Do', wip: 5 }, { id: 'doing', label: 'In Progress', wip: 3, color: 'warning' }, { id: 'done', label: 'Done', color: 'success' },]; // Assigning .items diffs against current cards.board.items = [ { id: 'PROJ-1', column: 'todo', title: 'Wire auth', type: 'task', priority: 'high' }, { id: 'PROJ-2', column: 'doing', title: 'Refactor cache', type: 'feature', priority: 'medium' },]; // Avoid feedback loops by ignoring drag-sourced changes:board.addEventListener('kanban-board:items-changed', (e) => { if (e.detail.source === 'drag') updateMyState(e.detail.items);});
See the Data API concepts guide for the full design rationale, the diff algorithm's preservation guarantee, and how to migrate an imperative integration to the property API.
JSON Data Mode
Set the src attribute to load board data from a JSON URL. Items with storyId fields are rendered as <user-story> elements with persona and action content in slots. All other items are rendered as <article> elements.
{ "columns": [ { "id": "backlog", "label": "Backlog", "wip": null, "color": null, "items": [ { "id": "task-1", "text": "Design review" }, { "id": "PROJ-201", "storyId": "PROJ-201", "persona": "Sarah Chen", "action": "view timelines", "priority": "high", "detail": "minimal" } ] } ]}
Keyboard Navigation
All keyboard support is provided by the underlying <drag-surface> component:
- Tab to focus a column's drag surface
- Space or Enter to grab an item
- Up / Down to reorder within a column
- Left / Right to transfer between columns
- Space to drop the item
- Escape to cancel
Accessibility
- The columns container has
role="region"witharia-label="Kanban board" - Each drag-surface has an
aria-labelmatching the column label - A visually hidden live region announces all transfers for screen readers
- Count badges use
<output>elements for live value updates - Respects
prefers-reduced-motion: reduce
Retrospective recipe
Start / Stop / Continue retrospective boards are just <kanban-board> with three named columns. The component already supports arbitrary data-column children — there's no separate retrospective-board component needed.
<kanban-board> <section data-column="start" data-column-label="Start" data-column-color="success"> <article class="retro-card" draggable="true" data-id="r-1">Pair-program on tricky migrations</article> </section> <section data-column="stop" data-column-label="Stop" data-column-color="warning"> <article class="retro-card" draggable="true" data-id="r-2">Skipping standup notes</article> </section> <section data-column="continue" data-column-label="Continue" data-column-color="info"> <article class="retro-card" draggable="true" data-id="r-3">Demo-driven dev review every Thursday</article> </section></kanban-board>
Related
<drag-surface>— the underlying drag-and-drop engine<story-map>— horizontal story map with activity columns<impact-effort>— 2×2 prioritization matrix<user-story>— Agile story cards to use as board items