Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Columnar drag-and-drop board with user-defined columns, count badges, and optional WIP limits.
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>
| 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 |
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) |
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>
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>
| 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. |
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.
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" } ] } ]}
All keyboard support is provided by the underlying <drag-surface> component:
role="region" with aria-label="Kanban board"aria-label matching the column label<output> elements for live value updatesprefers-reduced-motion: reduceStart / 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>
<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