KQ-1303 · SWIM/BOARD
Swim Board
A kanban board with swimlanes. A dragged card comes up into a carried copy 1:1 under the pointer, scaled on flick and leaning into its horizontal speed up to tilt degrees on a glide-tuned spring; the cell under it opens a gap of the card's exact height on glide while the cards below slide down, and on release it flies into the gap on snap with the release velocity and straightens as it lands. A move past a column's work-in-progress limit shakes its header on a five-beat tween and turns it warn (or is refused, the gap staying home), lanes group by priority or owner and fold on glide, and moving a card between lanes rewrites that field. Space picks a focused card up, arrows carry it a place or a column at a time, Enter drops and Escape puts it back, every step announced.
Sprint 14
Fernworks Field appSpace picks a card up. Arrow keys move it a place or a column, Enter drops it, Escape puts it back. Arrow keys alone move between cards.
sprint 14 · drag a card, or focus one and press space to pick it up
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/swim-board
Props
- cards / defaultCards
- SwimCard[]
- default defaultSwimCards
- Every card in board order — a cell lists its cards in the order they appear here. Each has a title, column, priority, owner, and optional tag and points. Controlled or uncontrolled.
- onCardsChange
- (cards: SwimCard[], move: SwimMove) => void
- default —
- Fires from the drop that moved a card, with every card and the move (from and to as column, lane and index, and whether it came from the pointer or the keyboard).
- columns
- SwimColumn[]
- default defaultSwimColumns
- The columns left to right, each with an optional work-in-progress limit.
- people
- SwimPerson[]
- default defaultSwimPeople
- Everyone a card can belong to; owner lanes follow this order.
- onMove
- (move: SwimMove) => void
- default —
- A card was dropped somewhere new.
- onCardOpen
- (id: string) => void
- default —
- A card was tapped, clicked, or had Enter pressed on it while not picked up.
- onLaneToggle
- (lane: string, collapsed: boolean) => void
- default —
- A lane was folded or unfolded, with its new state.
- tilt
- number
- default 6
- How far a carried card leans into its travel, in degrees: its angle is the pointer's horizontal speed, clamped to this. 0 keeps it level.
- limit
- "warn" | "block" | "off"
- default "warn"
- Work-in-progress limits: a move past one shakes the header and turns it warn, is refused with the card going home, or limits are hidden.
- lanes
- "priority" | "owner" | "none"
- default "priority"
- What the rows group by. Moving a card between lanes rewrites that field on the card.
- collapsedLanes / defaultCollapsedLanes
- string[]
- default []
- Folded lanes by id. Controlled (with onLaneToggle) or uncontrolled.
- title
- string
- default "Sprint 14"
- The board's heading.
- subtitle
- string
- default "Fernworks Field app"
- A quieter line beside the heading, from 480px.
- status
- "ready" | "loading" | "error"
- default "ready"
- Skeleton cards while loading; a message with Retry on error.
- onRetry
- () => void
- default —
- The error state's Retry button.
- label
- string
- default title
- The board's accessible name.
- sound
- boolean
- default false
- A click as a card comes up or a lane folds, a thock pitched by column as it lands — duller when a drop is refused.
- disabled
- boolean
- default false
- Cards can still be read and walked with the arrow keys, but not carried or opened.
- className
- string
- default —
- Classes for the root. It is 560px tall by default; the lanes scroll inside it.
| Prop | Type | Default | Description |
|---|---|---|---|
| cards / defaultCards | SwimCard[] | defaultSwimCards | Every card in board order — a cell lists its cards in the order they appear here. Each has a title, column, priority, owner, and optional tag and points. Controlled or uncontrolled. |
| onCardsChange | (cards: SwimCard[], move: SwimMove) => void | — | Fires from the drop that moved a card, with every card and the move (from and to as column, lane and index, and whether it came from the pointer or the keyboard). |
| columns | SwimColumn[] | defaultSwimColumns | The columns left to right, each with an optional work-in-progress limit. |
| people | SwimPerson[] | defaultSwimPeople | Everyone a card can belong to; owner lanes follow this order. |
| onMove | (move: SwimMove) => void | — | A card was dropped somewhere new. |
| onCardOpen | (id: string) => void | — | A card was tapped, clicked, or had Enter pressed on it while not picked up. |
| onLaneToggle | (lane: string, collapsed: boolean) => void | — | A lane was folded or unfolded, with its new state. |
| tilt | number | 6 | How far a carried card leans into its travel, in degrees: its angle is the pointer's horizontal speed, clamped to this. 0 keeps it level. |
| limit | "warn" | "block" | "off" | "warn" | Work-in-progress limits: a move past one shakes the header and turns it warn, is refused with the card going home, or limits are hidden. |
| lanes | "priority" | "owner" | "none" | "priority" | What the rows group by. Moving a card between lanes rewrites that field on the card. |
| collapsedLanes / defaultCollapsedLanes | string[] | [] | Folded lanes by id. Controlled (with onLaneToggle) or uncontrolled. |
| title | string | "Sprint 14" | The board's heading. |
| subtitle | string | "Fernworks Field app" | A quieter line beside the heading, from 480px. |
| status | "ready" | "loading" | "error" | "ready" | Skeleton cards while loading; a message with Retry on error. |
| onRetry | () => void | — | The error state's Retry button. |
| label | string | title | The board's accessible name. |
| sound | boolean | false | A click as a card comes up or a lane folds, a thock pitched by column as it lands — duller when a drop is refused. |
| disabled | boolean | false | Cards can still be read and walked with the arrow keys, but not carried or opened. |
| className | string | — | Classes for the root. It is 560px tall by default; the lanes scroll inside it. |