KQ-1723 · BLOCKER/CHAINS
Blocker Chains
A kanban board whose blocked cards hang a thin chain to the card they wait on: quadratic catenaries drawn under the cards with a fixed length, so dragging a card pulls its chains taut and bringing it closer lets them droop. Moving a blocker into Done snaps its chains loose — the free end swings down from the waiting card as a damped pendulum and fades on the exit ease — while the freed card lifts on springs.snap with an unblocked glow and settles back on springs.glide, and every card that moved FLIPs to its place on glide. Cards share one roving tab stop: arrows move, Space lifts and drops, Escape puts back, B shows every chain, L links a blocker, and Tab reaches a card's chain chips where Enter jumps to the blocker as a bead runs along the chain and Delete unlinks.
Fieldline 4.2
Release board · 10 cards
Arrows move between cards. Space lifts a card, arrows carry it, Space drops it and Escape puts it back. L links a blocker, B shows every chain, and Tab reaches a card's chains: Enter jumps to the blocker, Delete unlinks.
5 blocked · 1 loop · chains on focus
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/blocker-chains
Props
- columns
- BlockerColumn[]
- default defaultBlockerColumns
- The columns, left to right: { id, title, done? }. A done column finishes work, and the chains its new cards held snap loose.
- cards / defaultCards
- BlockerCard[]
- default — / defaultBlockerCards
- The cards, controlled or initial, in column order: { id, title, column, blockedBy?, assignee?, since? }.
- onCardsChange
- (cards: BlockerCard[], change: BlockerChange) => void
- default —
- Fires with the whole board after a move, a link or an unlink, and what changed.
- onMove
- (move: { cardId, from, to, index }) => void
- default —
- Fires from the drop, pointer or keyboard, that moved a card.
- onLink
- (blockedId: string, blockerId: string, info: { circular: boolean }) => void
- default —
- Fires when a blocker is linked by the handle or by L; circular says the new chain closes a loop.
- onUnlink
- (blockedId: string, blockerId: string, reason: "done" | "removed") => void
- default —
- Fires when a chain comes off: its blocker reached a done column, or Delete removed it.
- showAll / defaultShowAll
- boolean
- default — / false
- Every chain in full, rather than only the hovered or focused card's (the rest stay faint).
- onShowAllChange
- (showAll: boolean) => void
- default —
- Fires from the Chains switch or the B key.
- chainStyle
- "links" | "thread" | "beads"
- default "links"
- How a chain is drawn: hollow interlocking links, one fine thread, or round beads.
- cardStyle
- "raised" | "flat" | "outline"
- default "raised"
- Card surface: raised with a shadow, a flat tint, or an outline on the board colour.
- accent
- string
- default "var(--accent-bright)"
- Highlighted chains and shackles, the drop wash, the switch and link mode. Any CSS colour.
- now
- number
- default Date.UTC(2026, 9, 16, 9)
- The moment a card's `since` is read against for "blocked 3d", in ms. Never the wall clock.
- title
- string
- default "Fieldline 4.2"
- The board's heading.
- subtitle
- string
- default "Release board"
- One line under the heading, followed by the card count.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the board has loaded: loading shows skeleton columns, error a message with Try again.
- onReload
- () => void
- default —
- Try again was pressed after the board failed to load.
- disabled
- boolean
- default false
- Read-only: nothing moves or links, chains still show.
- className
- string
- default —
- Classes for the outer container.
| Prop | Type | Default | Description |
|---|---|---|---|
| columns | BlockerColumn[] | defaultBlockerColumns | The columns, left to right: { id, title, done? }. A done column finishes work, and the chains its new cards held snap loose. |
| cards / defaultCards | BlockerCard[] | — / defaultBlockerCards | The cards, controlled or initial, in column order: { id, title, column, blockedBy?, assignee?, since? }. |
| onCardsChange | (cards: BlockerCard[], change: BlockerChange) => void | — | Fires with the whole board after a move, a link or an unlink, and what changed. |
| onMove | (move: { cardId, from, to, index }) => void | — | Fires from the drop, pointer or keyboard, that moved a card. |
| onLink | (blockedId: string, blockerId: string, info: { circular: boolean }) => void | — | Fires when a blocker is linked by the handle or by L; circular says the new chain closes a loop. |
| onUnlink | (blockedId: string, blockerId: string, reason: "done" | "removed") => void | — | Fires when a chain comes off: its blocker reached a done column, or Delete removed it. |
| showAll / defaultShowAll | boolean | — / false | Every chain in full, rather than only the hovered or focused card's (the rest stay faint). |
| onShowAllChange | (showAll: boolean) => void | — | Fires from the Chains switch or the B key. |
| chainStyle | "links" | "thread" | "beads" | "links" | How a chain is drawn: hollow interlocking links, one fine thread, or round beads. |
| cardStyle | "raised" | "flat" | "outline" | "raised" | Card surface: raised with a shadow, a flat tint, or an outline on the board colour. |
| accent | string | "var(--accent-bright)" | Highlighted chains and shackles, the drop wash, the switch and link mode. Any CSS colour. |
| now | number | Date.UTC(2026, 9, 16, 9) | The moment a card's `since` is read against for "blocked 3d", in ms. Never the wall clock. |
| title | string | "Fieldline 4.2" | The board's heading. |
| subtitle | string | "Release board" | One line under the heading, followed by the card count. |
| status | "ready" | "loading" | "error" | "ready" | Whether the board has loaded: loading shows skeleton columns, error a message with Try again. |
| onReload | () => void | — | Try again was pressed after the board failed to load. |
| disabled | boolean | false | Read-only: nothing moves or links, chains still show. |
| className | string | — | Classes for the outer container. |