Segmented Control
A small set of mutually exclusive choices shown at once, built from real radio inputs in a fieldset — so arrow keys, form submission and screen-reader announcement all work without any script.
src/components/ui/segmented-control.tsxAbout
From the component's own source, where it was written.
A small set of mutually exclusive choices, shown all at once.
**Not a button group, and not a tab list.** A button group is a row of independent actions; tabs switch which panel is visible and carry aria-controls. This picks a *value* — day/week/month, grid/list — so it is a radio group, and it is built as one: real <input type="radio"> elements in a <fieldset>. That gives arrow-key navigation, form submission, :checked, and screen-reader announcement of "2 of 3" for free, none of which a row of <button>s with aria-pressed gets right.
**Use it below about five options, with short labels.** Past that a Select is faster to read and does not fight for horizontal space; this is the control you reach for when seeing all the options at once is the point.
The moving indicator is one absolutely-positioned element sized by the selected index, rather than a background on the selected item — so it slides between options instead of blinking, and there is one thing to animate.
Install
npx astralyx-ui add segmented-control
Usage
import { SegmentedControl } from '@/components/ui/segmented-control'
<SegmentedControl label="View" options={options} value={view} onValueChange={setView} />Composer
Change the props and the source below follows.
<SegmentedControl
label="View"
options={options}
size="default"
fullWidth={false}
/>Examples
Choosing a view
<SegmentedControl label="View" options={options} />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
SegmentedControl
Extends Omit<ComponentProps<'fieldset'>, 'onChange' | 'defaultValue'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| disabled | boolean | — | — |
| name | string | — | — |
| options | Segment[] | required | { value, label, srLabel?, disabled? }. Keep it under about five with short labels. |
| value | string | — | — |
| defaultValue | string | — | — |
| onValueChange | (value: string) => void | — | — |
| label | string | — | Accessible name for the group. Required unless one is supplied elsewhere. |
| size | SegmentedControlSize | 'default' | The Button's scale: the track is the height of the Button of the same size, so a segmented control sits level in a toolbar. md is kept as another name for default. |
| fullWidth | boolean | false | Fill the container, splitting the width evenly. |
Types
Segment
A small set of mutually exclusive choices, shown all at once.
| Field | Type | Description |
|---|---|---|
| value | string | — |
| label | ReactNode | — |
| srLabel? | string | Announced when the visible label is an icon. |
| disabled? | boolean | — |
| Type | Definition | Description |
|---|---|---|
SegmentedControlSize | SegmentSize | 'md' | — |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| radios, not buttons | fieldset | It picks a value, so it is a radio group. A row of buttons with aria-pressed gets arrow keys, :checked and form submission wrong. |
| vs ButtonGroup / Tabs | different jobs | A button group is independent actions; tabs switch a visible panel and carry aria-controls. This sets a value. |
| indicator | one element | Positioned by index rather than a background on the selected item, so it slides instead of blinking and there is one thing to animate. |