Navigation
Segmented control
One choice out of a few, in a recessed track with a thumb resting in it. Two or five options, all visible at once — past that it is a select.
Usage
import { SegmentedControl } from '@carabine/ui/segmented-control';The track is a hole and the thumb is an object resting in it. That is the whole idea: opaque recess, inset shadows rather than a border, and a thumb that carries a hairline because it is on top of something.
Clipped, not timed
Each label is drawn twice — once in the resting colour, once in the selected one — and the selected copy is clipped to the thumb. So a word the thumb is halfway across is half of each colour, exactly, at every frame.
The obvious alternative is to fade the label at the moment the thumb passes it, and it is wrong in a way you feel before you can name: the crossfade has a duration of its own, so it lags the thumb going one way and leads it coming back. Clipping has no timing to get wrong, because there is nothing to time — there is only where the thumb is.
Examples
Icons
Full width
Customization
<SegmentedControl
items={items}
size={40} // track height; everything follows it
radius={12} // the thumb takes this minus the padding
padding={4} // between the track's edge and the thumb
full
stiffness={460} // the thumb's spring
damping={40}
theme="light"
/>Styling reference
The recess is opaque — #0e0e11 dark, #f3f3f4 light — and it is the same material as the switch's track and the text field's well. Three components that are holes, painted the same, so a form does not read as three different depths.
API reference
| Prop | Type | |
|---|---|---|
| className | string | Added to the component’s own classes, so yours wins |
| style | CSSProperties | Merged after the component’s own inline styles, so yours wins |
Colours are data and dimensions are props, and neither covers a margin, a font, or a class from your own system. That is what these are for. State is legible from CSS as well — data-state, data-disabled, data-side — so a rule can answer it without knowing a single class name of ours. See Theming.
| Prop | Type | Default | |
|---|---|---|---|
| items | SegmentItem[] | [] | |
| value | string | — | Controlled |
| defaultValue | string | first enabled | |
| onValueChange | (value: string) => void | — | |
| size | number | 34 | Track height; everything follows it |
| radius | number | size × 0.3 | The thumb takes this minus the padding |
| padding | number | 3 | Between the track’s edge and the thumb |
| full | boolean | false | Spreads the options across the width |
| stiffness | number | 460 | The thumb’s spring |
| damping | number | 40 | |
| disabled | boolean | false | |
| label | string | 'Options' | Accessible name of the group |
| theme | 'dark' | 'light' | 'dark' |
SegmentItem carries id, label, icon and disabled.
Keyboard
| Key | |
|---|---|
| Tab | One stop for the whole control — it is one control |
| ← → | Walks the options, wrapping, skipping disabled ones |
| Home / End | First and last |