Kanban
Board vocabulary for planning views — the projection model: columns are values of a field (status, assignee, priority…), the board owns zero data, and every move dispatches lb-board-move {card, from, to, index} for your store to persist. Cards keep a fixed slot layout (title → labels → assignee + due) so dense boards stay scannable. Menu-move first: each card's kebab carries auto-built "Move to column" items, so no move depends on dragging. Column counts self-maintain. Pointer drag works with mouse/pen (the live card is the drop preview; two-axis edge auto-scroll); touch uses the kebab menu. Keyboard: focus a card, Space or Enter grabs it, arrows move it (←→ across columns, ↑↓ within), a second Space or Enter drops it, Esc cancels and restores — every step announced.
a11y (built in): cards are focusable grab targets (tabindex="0") — Space/Enter grabs, arrows move (←→ across columns, ↑↓ within), Space/Enter drops, Esc restores the original column and index, and focus rides the card through every step. Menu, pointer drag and keyboard each announce their result through a single visually-hidden aria-live="polite" region — there is no ARIA drag-and-drop vocabulary, so authored announcements are the pattern. The card kebab is a real disclosure menu (aria-haspopup=menu + aria-expanded, role=menuitem items, arrows/Home/End, Esc closes and returns focus to the trigger) revealed on :focus-within as well as hover — it is the single-pointer non-drag path WCAG 2.5.7 requires, since pointer drag refuses touch and runs mouse/pen only.
Usage: Load js/lb.js first, then js/components/lb-board.js; the board auto-inits on data-lb-board. Markup contract: columns are .lb-board__column with data-lb-board-column="Name" (that name is the vocabulary the move event speaks), each wrapping a .lb-board__column-body of cards carrying data-lb-board-card tabindex="0" — the tabindex is required for keyboard grab; a header badge with data-lb-board-count is auto-maintained and data-lb-board-wip="n" opts a column into the advisory limit. Kebab move-menus and counts are derived chrome rebuilt by el._lbBoard.refresh() — call it after adding or removing cards/columns yourself; programmatic moves go through moveCard(card, column, index), the single mutation path shared with drag, keyboard and menu. When lb-board-move fires the DOM move has already happened — write it through to your store, or call moveCard back to revert a rejected save.
Consumed here: Card (--sm --interactive) · Badge (labels + counts) · Avatar · Menu (kebabs) · Button (add-card ghost) · Empty State. Not bundled, by design: the data layer and realtime sync are yours — same engine boundary as the Media Player.
Board
Try it: open any card's kebab (appears on hover or keyboard focus) and move it — counts update, the move is announced, and lb-board-move fires (shown below the board).
Nothing in review
Cards land here when work is ready.
Card anatomy — the property vocabulary
A board card is a title plus properties that render only when set. Every element below composes from existing pieces — cover (.lb-card__media + ratio variants), plain mono ID, priority badge, checklist progress (.lb-progress-inline), quiet icon+count stats, .lb-avatar-group, and due-date states (the one universally-shipped signal: --overdue danger · --soon warning · --done success — never red on completed cards — once work is done, a past due date is history, not a failure). The "In progress" column above also demos the advisory WIP limit (data-lb-board-wip — it communicates, never blocks; move a card in to see 3/2 flip to warning). Deliberate skips: watchers/votes, epic lozenges, inline editing, class-of-service card colors (labels carry that here), aging/time-in-status (needs data the DS doesn't own).

Last move: —