A segmented control is a radio group that looks like a switch: one choice out of a short, fixed set, all of them visible at once, with a thumb that slides to whichever one is current. It changes how something is shown, and does not navigate away from it.
When to use
Use Segmented when every option fits on one line, the set never changes under the reader, and switching is cheap enough to be worth trying.
Switching the view over one dataset: table, board, calendar. The content stays the same, only its shape changes.
Filtering one list by a small, fixed set of predicates: all, unread, mentions.
Picking a range or a unit that the surface around it re-reads: daily, weekly, monthly; monthly against yearly billing.
Setting a mode in a toolbar: text alignment, theme, device width. These are the icon-only cases.
Do not use when
There are more than about five options, or the labels are long enough to wrap. The track has to fit on one line.
The set of options is loaded, searched, or user-defined.
More than one can be chosen at once. That is a Checkbox group.
Choosing navigates somewhere: a different URL, a different object, a different step. That is Tabs, or a menu.
Segmented, Tabs, Radio or Select
All four render “one of a short list”. Picking between them depends on what the choice means, not on how it looks.
Segmented changes how one thing is displayed. The content underneath is the same content.
Tabs move between different panels of content, each with its own heading. A tab is a place, and a segment is a setting.
Radio.Group with optionType="button" is the same DOM (radios in labels) at a different weight: it reads as a form field, so it belongs inside a form, next to a label, submitted with the rest. Segmented reads as a control on the page.
Select once the set is long, searchable, or not worth the horizontal space.
A Segmented that fills its parent past about four equal segments stops reading as a segmented control and starts reading as a tab bar. If that is what you meant, use Tabs, which brings the roles and the arrow-key behavior a tab list is supposed to have.
Labels
One or two words per segment, and parallel across the set: “Daily / Weekly / Monthly”, not “Daily / By week / Every month”.
Keep them the same part of speech and roughly the same length. An uneven track draws the eye to the widest segment rather than to the selected one.
label takes any node, so a count, a Badge dot or a Tag can sit beside the word. Keep it to one extra per segment. Two turn the track into a table.
Work out a count in a label from the same rule that filters, so a badge saying 2 beside a list showing 3 cannot happen.
A Tag has its own end margin, which doubles the gap in a flex row, so add me-0. Give a Badge size="small", since the default is taller than the label and grows the track.
A Dropdown inside an option's label also selects that option when it opens. If that is not what you want, put the menu beside the control.
Icons
Go icon-only only when the glyph is conventional: play, alignment, theme, device width. “Kanban” is not.
An icon-only option still needs its word, hidden with sr-only inside icon, with label left unset. An empty label still adds a gap and pushes the glyph off center. Add a tooltip so sighted users see the word on hover.
Option tooltips have no arrow unless you pass arrow: true, and one bubble slides along the row. Once any option has a tooltip, a Tooltip of your own inside a label joins that bubble too. Test option tooltips in a real browser, since the shared bubble does not render under jsdom or happy-dom.
Do not mix an icon-only segment with a labeled one in the same track. The set should read as one vocabulary.
Use an icon name where the kit has one, so the glyph follows whichever icon pack your project installs. An icon passed through as does not.
Size, shape and width
small in toolbars and table headers, medium as the default, large when the control is the main thing on the surface.
Pick one shape per surface and keep it. Shape is brand, not hierarchy.
Use block on narrow surfaces, where the track would otherwise sit in a corner of a full-width card. It fills its parent, so set the width on the parent.
orientation="vertical" when the control lives in a side rail. It is the same component, not a menu. It replaces the older vertical boolean, and wins when both are set.
Selection and state
There is always one selection. Segmented cannot be empty, so an empty result belongs to the panel underneath, not to the control.
Pass value and onChange when anything else needs the selection. With defaultValue the control keeps its own state and nothing else can change it. Add as const to the options array so onChange is typed with the option values.
Switching should be cheap and reversible. If it costs a round trip, show the panel's own loading state. Do not disable the track the reader just used.
Disable a single option rather than the whole control where you can, and pair it with a tooltip: a disabled segment with no explanation is a dead end.
Keep the selection when the reader comes back. A view switcher that resets to “Table” on every visit is a view switcher nobody uses.
Do not reorder the options in response to a selection. That moves the track under the reader's cursor.
Color alone never says which segment is current. The thumb's fill, its elevation and the text weight all carry the selection together, which is why a restyle that drops the thumb's background in favor of a tint usually fails contrast.
Restyling
Use classNames first (root, item, label, icon). The thumb has no key and needs a descendant selector.
Style the thumb and the selected item together. The thumb slides between options and the item takes over once it lands, so styling one alone changes the highlight mid-slide.
Use theme classes, not literal colors. A solid track built from bg-bg-solid and text-bg-base inverts on its own in dark mode, and a hardcoded black does not.
The theme palettes are a 1 to 10 scale. text-blue-6 and text-blue-600 look like typos of each other and are two different palettes.
A Segmented token set as a CSS variable in a class has no effect. Style the item instead.