Skip to content

Kanban ​

A board view of columns and cards: drag cards across columns or reorder them within a column, with column reordering, WIP limits, multi-select, and swimlanes on top; every settled move emits oas-change so the host can sync data. Fits staged workflows such as task boards, idea pools, and hiring pipelines.

Basic usage ​

Both columns (column definitions) and cards are passed as JSON attributes (property arrays work the same). Cards are grouped by their column field and render title by default:

Three-column board

Drag any card onto another column and drop it — the oas-change feedback shows up here.

Drag across columns and reorder ​

HTML5 DnD: drag a card onto a target column and the drop point shows a primary-color insertion indicator (top/bottom half of a hovered card selects before/after; empty columns highlight the tail). On drop the component writes the card data (new column plus new order) back to cards and emits oas-change with detail { id, from, to, index } — index is the insertion position in the target column (measured after removing the dragged card itself; hosts can reproduce with splice(index, 0, card) on the target array). Dropping back at the original spot is a no-op (no event).

Move and reorder (independently scrolling columns)

Setting height on the component makes each column body scroll independently; drag a card up/down inside a column to reorder.

Column reordering ​

Use the drag handle on the left of each column header to move whole columns (or focus the handle and press ←/→); the drop point shows a primary-color insertion line between columns. On settle the component writes the new order back to columns and emits oas-column-reorder with detail { from, to, keys } — from is the moved column's original position, to its new one, keys the full reordered list; card data is untouched. Dropping back at the original spot is a no-op. Touch devices (pointer: coarse) get move-left/move-right buttons inside the column header (boundary items disabled):

Column reordering

Drag the handle on a column header (or focus it and press ←/→) — the oas-column-reorder feedback shows up here.

WIP limits ​

Add limit (a number) to a column definition: when the column's card count exceeds it, the header count turns warning-colored and the column gets a data-over-limit marker. The limit is advisory, not blocking — dropping into an over-limit column still lands; whether to warn is up to the host. Unset, 0, or negative means unlimited:

WIP limit (In progress has limit=2, currently over)

The "In progress" column is 3/2 — over its limit: warning-colored count plus data-over-limit, yet cards can still be dropped in.

Multi-select ​

Ctrl/Cmd+click toggles cards into the selection; Shift+click selects a range (from the anchor card to the clicked one; both must be in the same column and the same lane band — across columns / lanes it degrades to single-select); selected cards get a primary-color outline. Drag any selected card and all selected cards move together to the drop column, emitting a single oas-change whose detail gains an ids array (id stays the first). Click blank space or press Esc to clear the selection; a plain click falls back to single-select. Touch devices have no multi-select (the move menu acts on its own card only):

Multi-select batch move

Ctrl/Cmd+click a couple of cards in "To do", then drag any of them to a target column — the batch-move feedback shows up here.

Swimlanes ​

swimlane-by="fieldKey" splits the board into horizontal bands by that field's value: each band has a header row (field value + count + fold arrow; click to fold/unfold, all expanded by default, fold state survives data changes) and its cards are grouped by column inside. Dropping a card into another band rewrites that field's value (oas-change detail gains swimlane: { from, to }); cards missing the value fall into the "(empty)" band and empty cells show a placeholder (bands derive from existing cards — no fully-empty band form). Note: the swimlane-by field name must not collide with the built-in column field (cross-band dragging rewrites that field — a same-named field would destroy column assignment). Hovering a collapsed band during a drag auto-expands it for drop targeting; cancelling the drag keeps it expanded (the user has seen the content — no rollback):

Swimlanes (grouped by priority)

Drag a card into another band (e.g. pull "Unscheduled task" into High) — the swimlane feedback shows up here; click a band header to fold it.

Empty columns ​

Empty columns show a "Drag cards here" placeholder (translated via locale); the empty-column-text attribute overrides it:

Empty column with custom placeholder

Custom card rendering (renderCard) ​

Only title renders by default; for richer cards (tags, owners, progress) use the renderCard property function: (card) => Node | string. A returned Node is mounted directly; a returned string renders as plain text (injection-safe). While the function is present the data is not reflected back to the cards attribute (functions cannot be serialized — the host owns the data); dragging still emits oas-change and positions/groups keep updating.

renderCard rich cards

Touch and keyboard ​

  • Touch devices (pointer: coarse) cannot drive HTML5 drag: every card gets a move button in its top-right corner that opens a small menu — move up / move down / move to a column (move up is disabled on the first card, move down on the last); column headers get move-left/move-right buttons for reordering (boundary items disabled);
  • Keyboard accessible: cards are focusable (tabindex=0), non-empty column bodies are role="list" (empty columns carry no list semantics), cards are role="listitem", with readable names from column titles and card titles; column reordering also works by focusing the column handle and pressing ←/→;
  • The move menu acts on its own card only (multi-select does not change menu behavior).

API ​

oas-kanban ​

Attributes ​

AttributeDescriptionTypeDefault
cardsCard data (JSON array [{ id, column, title, ...arbitrary fields }] where column is the owning column key). After a drop the component writes the new grouping/order back to this attribute (uncontrolled write-back + attribute reflection); skipped while a renderCard function is present (the host owns the data)KanbanCard[] | string[]
columnsColumn definitions (JSON array [{ key, title, limit? }]; the same-named property array channel works identically — the setter reflects back to the attribute). limit is a WIP limit: when the column's card count exceeds it, the header count turns warning-colored and the column gets a data-over-limit marker (advisory only — drops still land); unset/0/negative = unlimitedKanbanColumn[] | string[]
empty-column-textOverride for the empty-column / empty swimlane-cell placeholder text (defaults to the i18n kanban.emptyColumn key, translated with the locale)string—
swimlane-bySwimlane field key: splits the board into horizontal bands by that field's value (band header = fold arrow + field value + count; all expanded by default, fold state survives data changes); missing/empty values fall into the "(empty)" band; dropping a card into another band rewrites the swimlane field value; omit for the plain column layoutstring—

Property (JS property; see each row for attribute reflection) ​

PropertyDescriptionTypeDefault
renderCardCustom card renderer (card) => Node | string (property channel; functions cannot be serialized and stay out of attributes/SSR). A returned Node mounts directly; a returned string renders as plain text (injection-safe). While present, card data is not reflected back to the cards attribute (the host owns the data) while drag and the touch move menu still emit oas-change. Set to null to restore the default title renderingKanbanCardRenderer | null—

Events ​

EventDescription
oas-changeEmitted after a drag cross-column move, an in-column reorder, or a touch move-menu action settles (dropping back at the original spot emits nothing). detail { id, from, to, index } — from/to are column keys, index is the insertion position in the target column (measured after removing the moved cards; hosts can reproduce with splice(index, 0, card) on the target array); multi-select drags move all selected cards and emit one event whose detail gains ids (all selected ids in data order, id is the first); cross-swimlane drops also gain swimlane { from, to } (the swimlane field value change)
oas-column-reorderEmitted after a column reorder settles (column-head handle drag / keyboard ←/→ / touch move buttons; no-op at the original position emits nothing). detail { from, to, keys } — from is the moved column's original position, to its new position after the reorder, keys the full reordered list of column keys; the component also writes the new order back to the columns attribute (card data is untouched)

CSS Variables ​

CSS VariableDefault
--oas-kanban-column-min-height60px
--oas-kanban-column-min-width220px

Released under the MIT OR Apache-2.0 License.