Vanilla Breeze

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.

Attributes

AttributeTypeDescription
srcstringURL to JSON data for columns and items
titlestringOptional heading displayed above the board
compactbooleanReduced spacing variant for dashboard-style layouts

Child Attributes

Set these on <section> children to define columns:

AttributeOnTypeDescription
data-column<section>stringColumn identifier (e.g. "backlog", "doing", "done")
data-column-label<section>stringDisplay label for the column header. Falls back to title-cased data-column
data-wip<section>numberOptional WIP limit. Visual warning when exceeded
data-column-color<section>stringColor token for column tint: success, warning, error, info

Set these on draggable children within sections:

AttributeTypeDescription
draggable="true"booleanRequired for drag capability (auto-added if missing)
data-idstringStable 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.

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.

Events

EventDetailDescription
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:upgradedFired 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.

PropertyTypeDescription
.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) => ElementOptional custom item renderer. Default builds a <work-item> and applies fields via .data.

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.

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" with aria-label="Kanban board"
  • Each drag-surface has an aria-label matching 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.

Related