KQ-1512 · STRETCH/TABS
Stretch Tabs
A segmented control whose pill is rubber: its two edges are separate springs, so choosing another segment sends the leading edge out first to land with a slight overshoot while the trailing edge holds for a beat and then lets go on a softer spring — the pill stretching toward the new segment, thinning as it stretches, and squashing against the track's ends instead of leaving them. The selected label colour is a copy of the labels clipped to the pill's exact shape, so it sweeps across each letter with the rubber edge; at elastic 0 the pill is one piece on springs.snap. The pill can be dragged 1:1 (its trailing edge following on a short spring) and a release projects the throw and snaps to the nearest segment with the release velocity; arrows move and wrap, Home and End jump, in tablist or radiogroup semantics with a roving tabindex.
Basin BSN
421.15+2.2%
range 1 month · +2.2%
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/stretch-tabs
Props
- items
- StretchTabsItem[]
- default —
- The segments, in order: { value, label, ariaLabel?, controls?, disabled? }. `controls` is the id of the panel a tab shows.
- value / defaultValue
- string
- default — / the first item
- The selected value, controlled or initial.
- onValueChange
- (value: string) => void
- default —
- Fires from the press, key or drag that changed it, with the new value.
- label
- string
- default —
- The control's accessible name.
- segments
- number
- default items.length
- How many of the items are shown, from the first; the pill keeps its place in segment units, so a change needs no measuring.
- elastic
- number
- default 0.5
- The rubber, 0 to 1: how long the trailing edge holds, how soft it catches up, how far the pill may stretch (1.5 to 2.6 segments), how much it thins, and how much the leading edge overshoots. 0 is one rigid piece on springs.snap.
- size
- "sm" | "md" | "lg"
- default "md"
- Track height 32, 36 or 44px with 12, 13 or 14px labels.
- semantics
- "tablist" | "radiogroup"
- default "tablist"
- Tabs that show panels (role tab, aria-selected, aria-controls) or a radio group that picks a value (role radio, aria-checked).
- activation
- "auto" | "manual"
- default "auto"
- Arrow keys select as they move, or (tablist only) only move focus until Enter or Space.
- draggable
- boolean
- default true
- The pill can be dragged along the track; a drag that starts on another segment selects the segment it is released over.
- pill
- string
- default "var(--foreground)"
- The pill's colour, any CSS colour.
- pillText
- string
- default "var(--background)"
- The selected label's colour, on the pill.
- sound
- boolean
- default false
- A tick on every change and on every segment a drag crosses, pitched by position; only in answer to the visitor.
- disabled
- boolean
- default false
- Disables every segment.
- className
- string
- default —
- Classes for the track; it is inline-grid sized by its widest label, so pass w-full to stretch it.
| Prop | Type | Default | Description |
|---|---|---|---|
| items | StretchTabsItem[] | — | The segments, in order: { value, label, ariaLabel?, controls?, disabled? }. `controls` is the id of the panel a tab shows. |
| value / defaultValue | string | — / the first item | The selected value, controlled or initial. |
| onValueChange | (value: string) => void | — | Fires from the press, key or drag that changed it, with the new value. |
| label | string | — | The control's accessible name. |
| segments | number | items.length | How many of the items are shown, from the first; the pill keeps its place in segment units, so a change needs no measuring. |
| elastic | number | 0.5 | The rubber, 0 to 1: how long the trailing edge holds, how soft it catches up, how far the pill may stretch (1.5 to 2.6 segments), how much it thins, and how much the leading edge overshoots. 0 is one rigid piece on springs.snap. |
| size | "sm" | "md" | "lg" | "md" | Track height 32, 36 or 44px with 12, 13 or 14px labels. |
| semantics | "tablist" | "radiogroup" | "tablist" | Tabs that show panels (role tab, aria-selected, aria-controls) or a radio group that picks a value (role radio, aria-checked). |
| activation | "auto" | "manual" | "auto" | Arrow keys select as they move, or (tablist only) only move focus until Enter or Space. |
| draggable | boolean | true | The pill can be dragged along the track; a drag that starts on another segment selects the segment it is released over. |
| pill | string | "var(--foreground)" | The pill's colour, any CSS colour. |
| pillText | string | "var(--background)" | The selected label's colour, on the pill. |
| sound | boolean | false | A tick on every change and on every segment a drag crosses, pitched by position; only in answer to the visitor. |
| disabled | boolean | false | Disables every segment. |
| className | string | — | Classes for the track; it is inline-grid sized by its widest label, so pass w-full to stretch it. |