KQ-1281 · PANE/STACK

Pane Stack

Nested navigation that keeps the way back in view: opening a row slides its pane in from the right on springs.glide, and the pane it covers is pushed and compresses into a narrow labelled tab along the left edge, its content clipped rather than squashed while the tab face takes over. One motion value is the stack's position, so every pane edge, the staircase of covered tabs and the fold of tabs that no longer fit (with a count) are functions of it; a tab, Escape or a 1:1 swipe of the open pane to the right goes back, the throw projected and carried on glide with its release velocity. Rows are a list with one tab stop (arrows move, Enter, Space or ArrowRight open, ArrowLeft goes back), the tabs are a nav with their own stop, and focus moves into each new pane and back onto the row you came from.

KQ-1281 · PANE/STACK
Opens a pane. Left arrow or Escape goes back.

Plots

Measured this season

38

Plots · level 4 · 3 tabs back

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/pane-stack

Props

root
PaneStackNode
default defaultPaneTree
The tree to navigate: each node has an id, title, optional description, meta, icon, content and children. defaultPaneTree is a Fieldline workspace seven levels deep.
value / defaultValue
string
default the root's id
The open pane's id, controlled or initial.
onValueChange
(id: string, path: string[]) => void
default —
Fires from the press, key or swipe that asked to open a pane, with its id and the ids from the root to it.
onSelect
(id: string) => void
default —
A row with nothing inside it (no children, no content) was chosen.
label
string
default "Navigation"
The container's accessible name.
depth
number
default 0.5
How far covered panes recede, 0 to 1: tabs step in from top and bottom, shade toward the page and take the open pane's shadow. 0 is flat.
compress
number
default 40
Width of a covered pane's tab in px (28 to 64); from 36 up the tab shows its level's icon.
speed
number
default 1
Tempo of every slide, 0.5 to 1.5: the glide spring sped up or slowed down with its damping ratio kept.
minPaneWidth
number
default 200
The narrowest the open pane gets, in px; past it the oldest tabs fold under the first, which carries a count.
swipeBack
boolean
default true
Drag the open pane to the right to go back a level.
backLabel
(title: string) => string
default (title) => `Back to ${title}`
A tab's accessible name, from its pane's title.
emptyLabel
string
default "Nothing in here yet"
What a pane with no content and no children says.
height
number | string
default 440
The container's height, in px or any CSS length.
size
"sm" | "md" | "lg"
default "md"
Row height 36, 44 or 52 px; small rows drop the description line.
accent
string
default "var(--accent-bright)"
The trail back (the row you came from), the chosen row and the fold badge; any CSS colour.
sound
boolean
default false
A swish for each push and pop, a click for a tab, a chosen row and the swipe's half-way detent.
disabled
boolean
default false
Inert and dimmed: no presses, keys or swipes.
className
string
default —
Classes for the container.