SegmentedButton
Joins two or three choices into one control, with exactly one taken. Use it for short labels where every option should stay visible in little space.
import { Segment, SegmentedButton } from 'neba';
<SegmentedButton aria-label="Range" defaultValue="week">
<Segment value="day">Day</Segment>
<Segment value="week">Week</Segment>
<Segment value="month">Month</Segment>
</SegmentedButton>;Props
SegmentedButton
| Prop | Type | Default | Description |
|---|---|---|---|
| variantshared | 'solid' | 'outline' | 'text' | 'outline' | Weight of the trough. `solid` is a frosted trough with a filled tile, `outline` the same with a hairline and a lit tile, `text` no trough at all |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | The segments' height and type scale, on Button's own ladder |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. Arbitrary colour values are not accepted |
| densityshared | 'default' | 'compact' | 'default' | Padding only: never the height, never the type scale |
| elevationshared | 0 | 1 | 2 | 3 | 0 | Drop shadow depth of the trough. `0` is the default |
| value | string | number | null | — | The chosen segment, for a controlled set |
| defaultValue | string | number | null | null | Which starts chosen |
| onValueChange | (value: string | number | null) => void | — | Called when the chosen segment changes |
| disabled | boolean | false | Disables every segment at once |
| readOnly | boolean | false | Shows which one is chosen but does not let it be changed |
| name | string | — | Identifies the value when a form is submitted |
| fullWidth | boolean | false | The segments share the full width, each taking an equal part |
| children | ReactNode | — | The Segments |
value with onValueChange makes it controlled; defaultValue makes it uncontrolled. The set needs a name, so pass aria-label or aria-labelledby.
Segment
| Prop | Type | Default | Description |
|---|---|---|---|
| value * | string | number | — | Identifies the segment. What onValueChange reports |
| startIcon | ReactNode | — | Content before the label |
| endIcon | ReactNode | — | Content after the label: a count, a status dot |
| disabled | boolean | false | Unavailable, but still part of the set |
| children | ReactNode | — | The segment's label |
Examples
variant
solid rides a filled tile in a trough. outline is the same trough with a border, lighting the chosen sheet instead of filling it. text drops the trough and gives a surface only to the chosen segment.
import { Segment, SegmentedButton } from 'neba';
const variants = ['solid', 'outline', 'text'] as const;
export default function SegmentedButtonVariants() {
return (
<div className="flex flex-col items-center gap-5">
{variants.map((variant) => (
<SegmentedButton key={variant} aria-label={variant} variant={variant} defaultValue="week">
<Segment value="day">Day</Segment>
<Segment value="week">Week</Segment>
<Segment value="month">Month</Segment>
</SegmentedButton>
))}
</div>
);
}size
The same control heights as Button: a md segment and a md button are both 32px, so the two sit in one toolbar without the row losing its baseline.
import { Segment, SegmentedButton } from 'neba';
const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
export default function SegmentedButtonSizes() {
return (
<div className="flex flex-col items-center gap-4">
{sizes.map((size) => (
<SegmentedButton key={size} aria-label={size} size={size} defaultValue="on">
<Segment value="on">On</Segment>
<Segment value="auto">Auto</Segment>
<Segment value="off">Off</Segment>
</SegmentedButton>
))}
</div>
);
}startIcon · disabled · readOnly
readOnly shows which one is chosen but does not let it change, draining the saturation. disabled drops the colour family for neutral grey. disabled can also be set per Segment.
import { Segment, SegmentedButton } from 'neba';
function GridIcon() {
return (
<svg viewBox="0 0 16 16" fill="none">
<rect
x="2.5"
y="2.5"
width="4.5"
height="4.5"
rx="1"
stroke="currentColor"
strokeWidth="1.5"
/>
<rect x="9" y="2.5" width="4.5" height="4.5" rx="1" stroke="currentColor" strokeWidth="1.5" />
<rect x="2.5" y="9" width="4.5" height="4.5" rx="1" stroke="currentColor" strokeWidth="1.5" />
<rect x="9" y="9" width="4.5" height="4.5" rx="1" stroke="currentColor" strokeWidth="1.5" />
</svg>
);
}
function ListIcon() {
return (
<svg viewBox="0 0 16 16" fill="none">
<path
d="M5.5 4h8M5.5 8h8M5.5 12h8M2.5 4h.01M2.5 8h.01M2.5 12h.01"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
}
export default function SegmentedButtonStates() {
return (
<div className="flex w-full max-w-md flex-col items-center gap-5">
{/* Icons ride on the label, sized in `em`. */}
<SegmentedButton aria-label="Layout" defaultValue="grid">
<Segment value="list" startIcon={<ListIcon />}>
List
</Segment>
<Segment value="grid" startIcon={<GridIcon />}>
Grid
</Segment>
</SegmentedButton>
{/* One segment out of the set, without disabling the rest. */}
<SegmentedButton aria-label="Plan" defaultValue="team">
<Segment value="starter">Starter</Segment>
<Segment value="team">Team</Segment>
<Segment value="enterprise" disabled>
Enterprise
</Segment>
</SegmentedButton>
{/* Read-only keeps the choice and drains the saturation; disabled drops
the colour family entirely. */}
<SegmentedButton aria-label="Mode" defaultValue="auto" readOnly>
<Segment value="light">Light</Segment>
<Segment value="auto">Auto</Segment>
<Segment value="dark">Dark</Segment>
</SegmentedButton>
{/* The full width, divided evenly. */}
<SegmentedButton aria-label="Period" defaultValue="30d" fullWidth>
<Segment value="7d">7 days</Segment>
<Segment value="30d">30 days</Segment>
<Segment value="90d">90 days</Segment>
</SegmentedButton>
</div>
);
}fullWidth
Stretches the set to the container width, with the segments sharing the space equally.
Accessibility
- Renders
role="radiogroup". The set takes one tab stop, the arrow keys move within it, and the chosen segment carriesaria-checked. - The selection tile moves via
left·top·width·height, so no label is resampled. The first render and a window resize are not animated.
When to use something else
- A row of actions rather than a choice is a ButtonGroup.
- More than about five options, or long labels, wants a Select.
- If there are panels underneath, it is Tabs.
- If the set needs a visible label, a RadioGroup fits better.