KQ-1720 · LIMIT/LANES
Limit Lanes
A kanban board whose columns say no when they are full: every header carries a work-in-progress gauge — pills, a bar or a ring — that fills as cards arrive with each pill popping in on the snap spring, and the gauge is itself a slider you scrub or key to change the limit. A dragged card lifts on flick into a copy that follows the finger 1:1 while a placeholder opens and the cards round it glide aside; over a full column the column washes danger, and a drop there springs the card back home on snap with the throw's own velocity while the gauge pulses once. Arrows walk the cards, Space lifts one and arrows carry it through the layout past full columns, Space or Enter drops it and Escape puts it back.
Fernworks delivery
11 cards · 2 columns full
Arrow keys move between cards. Space picks a card up; arrows carry it, Space drops it, Escape puts it back. Enter opens a card. Each column's limit is a slider.
11 cards · Building 3/3 · Review 2/2 · Shipping 1/2
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/limit-lanes
Props
- columns
- LimitColumn[]
- default defaultLimitColumns
- The columns in order ({ id, title, limit?, status? }); a column without a limit takes anything, a loading one draws outlines.
- cards / defaultCards
- LimitCard[]
- default defaultLimitCards
- Controlled or initial cards ({ id, columnId, key, title, kind?, owner?, points? }); order within a column is the array's order.
- onCardsChange
- (cards: LimitCard[]) => void
- default —
- Fires with every card after a move.
- onMove
- (move: LimitMove) => void
- default —
- Fires from the drop or key that moved a card, with where it came from and went.
- onLimitChange
- (columnId: string, limit: number) => void
- default —
- Fires from the gauge scrub or key that changed a column's limit.
- onBlockedDrop
- (blocked: LimitBlocked) => void
- default —
- A card was dropped or keyed into a full column and sent back.
- onOpen
- (cardId: string) => void
- default —
- A card was tapped, or Enter was pressed on it.
- gaugeStyle
- "segments" | "bar" | "ring"
- default "segments"
- How each header draws its limit: one pill per slot, a continuous bar, or a ring of arcs.
- cardStyle
- "plain" | "edge" | "raised"
- default "edge"
- Cards drawn plain with a hairline, with a coloured edge by kind, or raised on a shadow.
- accent
- string
- default "var(--accent-bright)"
- Gauge fill, the lifted card's ring, the placeholder and open drop slots; any CSS colour.
- enforce
- boolean
- default true
- A full column refuses cards. Off, it takes them and shows the overrun in danger.
- maxLimit
- number
- default 8
- The highest a gauge can be set.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the board's cards have arrived; loading draws outlines in every column.
- onRetry
- () => void
- default —
- "Try again" was pressed after the board failed to load.
- title
- string
- default "Fernworks delivery"
- The heading.
- height
- number
- default 460
- The board's height in px; columns scroll inside it.
- label
- string
- default —
- The board's accessible name; defaults to the title.
- disabled
- boolean
- default false
- Freezes the board: no drags, keys or limit changes.
- className
- string
- default —
- Classes for the outer panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| columns | LimitColumn[] | defaultLimitColumns | The columns in order ({ id, title, limit?, status? }); a column without a limit takes anything, a loading one draws outlines. |
| cards / defaultCards | LimitCard[] | defaultLimitCards | Controlled or initial cards ({ id, columnId, key, title, kind?, owner?, points? }); order within a column is the array's order. |
| onCardsChange | (cards: LimitCard[]) => void | — | Fires with every card after a move. |
| onMove | (move: LimitMove) => void | — | Fires from the drop or key that moved a card, with where it came from and went. |
| onLimitChange | (columnId: string, limit: number) => void | — | Fires from the gauge scrub or key that changed a column's limit. |
| onBlockedDrop | (blocked: LimitBlocked) => void | — | A card was dropped or keyed into a full column and sent back. |
| onOpen | (cardId: string) => void | — | A card was tapped, or Enter was pressed on it. |
| gaugeStyle | "segments" | "bar" | "ring" | "segments" | How each header draws its limit: one pill per slot, a continuous bar, or a ring of arcs. |
| cardStyle | "plain" | "edge" | "raised" | "edge" | Cards drawn plain with a hairline, with a coloured edge by kind, or raised on a shadow. |
| accent | string | "var(--accent-bright)" | Gauge fill, the lifted card's ring, the placeholder and open drop slots; any CSS colour. |
| enforce | boolean | true | A full column refuses cards. Off, it takes them and shows the overrun in danger. |
| maxLimit | number | 8 | The highest a gauge can be set. |
| status | "ready" | "loading" | "error" | "ready" | Whether the board's cards have arrived; loading draws outlines in every column. |
| onRetry | () => void | — | "Try again" was pressed after the board failed to load. |
| title | string | "Fernworks delivery" | The heading. |
| height | number | 460 | The board's height in px; columns scroll inside it. |
| label | string | — | The board's accessible name; defaults to the title. |
| disabled | boolean | false | Freezes the board: no drags, keys or limit changes. |
| className | string | — | Classes for the outer panel. |