Workflow boards: columns are states, a drag is a transition, and the data-view shell supplies everything around the board.
Page-class laws·docs/kanban.md·102 lines
Kit src/components/kanban/Living example · Kanban
How every kanban / workflow-board page is built. The kit is src/components/kanban/ (+ the data-view kit for the shell); the living examples are the CRM deals pipeline (/labs/apps/crm/deals) and the neutral demo (/labs/layout/kanban). Same contract as data-view.md: the kit enforces what it can with defaults, this file records the rest.
Not to be confused with the Dashboard class: a dashboard is a free grid of independent widgets, a kanban is a row of columns with meaning — each column is a workflow state, and moving a card between columns IS a state change on the record.
01
A kanban page is a data view whose body is a column scroller. The outer Card, the toolbar (search left, module controls, canonical order) and the count semantics all come from the data-view kit unchanged; this law adds only the body grammar — columns, cards, and the drag that mutates state.
02
const kb = useKanban<StageKey>({ columnOf, onMove });
<DataViewShell>
<DataViewToolbar search={<DataViewSearch … />} actions={…} />
<KanbanBody>
{COLUMNS.map((c) => (
<KanbanColumn key={c.key} label={c.label} accent={…} count={n}
aside={fmtMoney(total)} limit={…}
isOver={kb.overColumn === c.key} {...kb.columnProps(c.key)}
footer={…}>
{items.map((it) => (
<KanbanCard key={it.id} dragging={kb.draggingId === it.id}
{...kb.cardProps(it.id)}>
…record content…
</KanbanCard>
))}
</KanbanColumn>
))}
</KanbanBody>
</DataViewShell>03
| Surface | What it is | Chrome |
|---|---|---|
| Board | ONE feature in ONE standard Card | DataViewShell — the card edge is the only solid feature boundary |
| Column | A well: a muted drop-target control, not a feature card | bg-card/60 + hairline border (control outline, same vocabulary as inputs and chips) |
| Item | The record's own card surface | bg-background, hover lift, in-flight fade |
04
useKanban filters same-column drops, so onMove only fires for real state changes — and every one confirms with a toast (the moved card alone can be missed once columns scroll).limit renders the count as n / limit, amber at the limit, red over it. No modal, no blocking — the board shows the overload, the team decides.05
PIPELINE-like: key, label, accent); items carry a column key.moved override map; useKanban gets columnOf + onMove, and onMove writes the override and toasts.DataViewToolbar, summary figures in its right lane.06
Cards, or an item surface that isn't the record's card.