Segmented
Segmented control — a radio group drawn as one strip of adjacent segments.
How this differs from what it looks like
Not select
A strip holds two or three options of a word or two, and past that a select is the right control. This is a measurement rather than a preference: four options reading "Poco resistente / Medio / Resistente / Molto resistente" are 472px of strip, so under about 480px the last one is off the end — and the case that found it was a printing option the help text underneath was recommending, on a phone, where it could not be reached. It goes unnoticed because a strip that does not fit does not look broken; it looks like a strip with three options in it. The component will not wrap to rescue you — a choice that reflows into two rows stops reading as one control, which is the whole line between this and chip-group — and it scrolls instead of clipping, which makes the failure survivable rather than good. Reach for select when the labels are sentences, when there are five, or when the set can grow.
Not button (.btn-group)
Both draw a strip of adjacent controls and they are not interchangeable. .btn-group is a row of actions — three buttons that each do something, or a set of toggles that can each be on independently. segmented is a radio group: exactly one of its options is chosen at any moment, choosing one un-chooses the rest, the arrow keys move between them, the whole strip is one Tab stop, and it submits a value. So the question is never "which looks right" but "is this a choice or a set of actions". That is also why .btn-group stays in button.css: it is not a component, it is two or more buttons with their corners joined, and every variant, size and state of .btn already applies to it.
Examples
<fieldset class="segmented">
<legend class="sr-only">View</legend>
<input class="segmented-input" type="radio" name="demo-view" id="demo-v-list" checked />
<label class="segmented-item" for="demo-v-list">List</label>
<input class="segmented-input" type="radio" name="demo-view" id="demo-v-grid" />
<label class="segmented-item" for="demo-v-grid">Grid</label>
<input class="segmented-input" type="radio" name="demo-view" id="demo-v-map" />
<label class="segmented-item" for="demo-v-map">Map</label>
</fieldset>Arrow keys move between the segments because they are radios. Nothing here implements a keyboard handler.
<fieldset class="segmented segmented-even segmented-sm">
<legend class="sr-only">Range</legend>
<input class="segmented-input" type="radio" name="demo-span" id="demo-s-d" checked />
<label class="segmented-item" for="demo-s-d">Day</label>
<input class="segmented-input" type="radio" name="demo-span" id="demo-s-w" />
<label class="segmented-item" for="demo-s-w">Week</label>
<input class="segmented-input" type="radio" name="demo-span" id="demo-s-m" />
<label class="segmented-item" for="demo-s-m">Month</label>
</fieldset>Tokens 12
Level 2, declared on .segmented itself. Set any of them on that selector to restyle this component without touching the skin.
| Token | Default |
|---|---|
| --segmented-bg | var(--color-surface-sunken) |
| --segmented-border-color | var(--color-border) |
| --segmented-item-bg | transparent |
| --segmented-item-color | var(--color-on-surface-muted) |
| --segmented-item-font-size | var(--font-size-sm) |
| --segmented-item-min-size | var(--target-size-compact) |
| --segmented-item-padding-block | var(--space-2) |
| --segmented-item-padding-inline | var(--space-4) |
| --segmented-padding | var(--space-1) |
| --segmented-radius | var(--radius-field) |
| --segmented-selected-bg | var(--color-surface-raised) |
| --segmented-selected-color | var(--color-on-surface) |
Variants and states
Variants
.segmented-lg.segmented-sm
Inside it
.segmented-item.segmented-input.segmented-icon.segmented-even
State it reads
Read from the platform, never mirrored into a class that could disagree with it.
:checked:disabled:focus:hoverdata-legacy
Before you ship it
What you have to do 1 requirements
- Name the group — a
<legend>inside<fieldset>, orrole="group"witharia-labelon any other container. Give one radiochecked: an empty radio group makes the first Tab land on the first segment instead of the chosen one, and there is no chosen one. An icon-only segment needs its label text in an<span class="sr-only">—.segmented-iconmakes the segments square, it does not supply the name that removing the text took away.
src/css/components/segmented.css · npx mostlycss add segmented