Segmented

One choice from a short list, with every option on screen at once.

import { Segmented, SegmentedItem } from "@dede-style/ui/segmented", then <Segmented value={v} onChange={setV} label="Text alignment"> wrapping a <SegmentedItem value="start" label="Start" /> per segment. It is controlled — the value lives with whoever owns the setting, and this renders it.

Reach for it when the options are few, short and worth comparing. Every one is on screen, so a reader decides by reading rather than by opening something — which is the whole of what it buys over a Select, and the whole of what it costs when there are more than a handful.

The selected segment is raised out of the track: the page’s own raised surface, a shadow step, primary ink and one weight up. Four signals for one state is more than a hover gets, and that is right — this one is persistent, and it has to read against its neighbours rather than only against itself.

It is a group of radios

The obvious build is a row of <button aria-pressed>, and it is the one that cannot be corrected later. A button can never take part in a form — no name, no value, nothing to submit — so the day a form needs one, swapping the element is a breaking change rather than a refactor. Both Switch and Select arrived at the same place.

Starting from <input type="radio"> costs nothing and hands over the entire keyboard contract already written. Radios sharing a name are one tab stop, not one per segment; the arrow keys move between them and select as they go; both directions wrap at the ends. Hand-writing that over pressed buttons is the bulk of the radiogroup pattern and is the half that gets subtly wrong.

Each input is invisible and still focusable — a transparent box filling its own segment, never display: none, which would take the control out of the focus order and with it every behaviour above. The wrapper is a radiogroup with a name of its own, rather than a fieldset: the first real consumer is a properties table, where there is nowhere for a legend to sit.

Two, three or four

Four is the ceiling, and it is arithmetic rather than taste.

  • Two
  • Three
  • Four

Every segment is equal width, always. So a track divides N ways, and past four the labels are too narrow to read at any size a control can use — which is why a fifth option throws in development rather than rendering something unreadable and letting a console warning be ignored. Equal widths are also what stop the raised segment resizing as the selection moves, which reads as the control breaking rather than as a choice being made.

A page rule, not a mechanism: single-word labels. It codifies what is already true — the longest option anywhere in this system is horizontal — so it will catch the first person to write “Align to start” rather than forcing a migration. Where a word is genuinely too long, abbreviate it and give the option an a11yLabel carrying the whole word: a reader hearing “H” is worse off than one looking at a wide control.

The properties tables on this site send only two-option props here and three or more to a Select. That threshold is the table’s, not the control’s — that column is too narrow to hold three readable labels, which is a fact about the table rather than about this control. Somewhere with room, three and four are exactly what this is for.

Naming the group

The segments name themselves. The group does not.

Abbreviated labels

label is required, for the reason it is required on an icon button: nothing on screen says what the segments are alternatives for. Without it a reader hears three unrelated radios and never learns they are choosing an alignment. A required prop makes that a build error rather than a review comment.

An abbreviation splits the visible label from the accessible name, which is why a11yLabel exists as a mechanism rather than as a style note. The track above shows H and V and announces Horizontal and Vertical. Leave it off and the visible words do the naming, which is the honest arrangement whenever they fit.

Disabling

One opacity over the whole group, and the selection survives it.

  • Enabled
  • Disabled

It is all or nothing. One opacity over the whole group, so the track, the segments and the raised one dim together and keep their relationship — which matters here more than on a button, because the one thing a disabled group must still say is which segment is selected. The pair above is the argument: the selection is as legible on the right as on the left.

There is no per-segment disable, and under an item component it would be a one-line addition — which is the reason to decline it deliberately rather than by omission. Segments are equal width, so a dead one still takes its full share of the track and reads as broken rather than as unavailable; and it has no way to say why, which is the gap a tooltip on a disabled control exists to fill.