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.

KQ-1720 · LIMIT/LANES

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.

Ready

5
No limit

Building

3 / 3
Full

Review

2 / 2
Full

Shipping

1 / 2
Room for 1

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.