Choice & selection

AppSegmented

Two to four mutually-exclusive options shown side by side, all visible at once.

<app-segmented label="Theme" value="dark"><app-segmented-option … /></app-segmented>

<app-segmented> shows a small set of exclusive choices with all of them visible — a theme switch, a date-range toggle above a chart, a list/grid view switch. Unlike a select, the reader sees every option without opening anything.

It is built on real <input type="radio"> elements inside labels. That is not an implementation detail: arrow-key navigation, the single tab stop for the whole group, and the “2 of 3 selected” screen-reader announcement all come from the browser — and because there is no JavaScript involved, the control posts inside a plain form.

Playground

Every attribute, live. Combine them freely — the generated call underneath is the exact call that produced the control above it. The selected value comes from clicking the preview itself, same as a real screen.

<AppSegmented
  label="Preview"
  options={[
    { value: '7d', label: '7 days' },
    { value: '30d', label: '30 days' },
    { value: '90d', label: '90 days' },
  ]}
  value={value}
  onChange={setValue}
/>
options swaps the whole arrayThe options control here picks between four preset children sets (plain, icons, notes, one option disabled) — in real code that's just the <app-segmented-option> children you author, not something a user ever changes at runtime.

Basic

Options as children, value for the selected one, and name for the field it posts as. label is the group's accessible name — required, because a bare row of pills tells a screen reader nothing about what is being chosen.

A range toggle
Three options is the comfortable case; four is the practical ceiling. Use the arrow keys once focused.
Show code
<app-segmented label="Date range" name="range" value="30d">
    <app-segmented-option value="7d" label="7 days" />
    <app-segmented-option value="30d" label="30 days" />
    <app-segmented-option value="90d" label="90 days" />
</app-segmented>
No JavaScript neededSelection is the native radio, so this works in a plain <form> post with nothing wired up. Give it a name and read the value on the server like any other field.

With icons

An icon speeds recognition for a familiar concept — a sun for light, a moon for dark. It sits beside the label, never replacing it.

The theme switch
The canonical use. Keep the text label: an icon-only segmented control forces the reader to guess.
Show code
<app-segmented-option value="light" label="Light" icon="sun" />
<app-segmented-option value="dark" label="Dark" icon="moon" />

With a note

An option can carry a note — a smaller, muted sub-line under the label for a supporting detail. Segments without one keep the plain single-line layout.

A sub-label per option
Show code
<app-segmented-option value="7d" label="7 days" note="Last week" />
<app-segmented-option value="30d" label="30 days" note="Last month" />

Sizes

size takes sm, lg or xl (md is the default). sm tightens the padding for a card header or table toolbar; lg and xl give a more prominent toggle, xl for a hero or settings screen.

Small, medium, large, extra large
Show code
<app-segmented size="sm" label="Date range" name="range" value="30d">…</app-segmented>
<app-segmented label="Date range" name="range" value="30d">…</app-segmented>
<app-segmented size="lg" label="Date range" name="range" value="30d">…</app-segmented>
<app-segmented size="xl" label="Date range" name="range" value="30d">…</app-segmented>

Full width

Set full-width to make the segments share the row equally and fill the container — for a toggle spanning a card or a form section.

Segments fill the row
Show code
<app-segmented full-width="true" label="View" name="view" value="list">…</app-segmented>

Without the ring

The selected option is an outlined pill by default. Set bordered="false" to drop the ring and keep only the faint tint and the primary label, for a quieter control in a dense toolbar.

bordered="false"
Show code
<app-segmented bordered="false" label="Range" name="range" value="30d">…</app-segmented>

Disabled

Disable the whole control with disabled, or keep a single option visible but unpickable with the option's disabled.

Whole control and a single option
Show code
<app-segmented disabled="true" label="View" name="view" value="7d">…</app-segmented>
<app-segmented-option value="map" label="Map" disabled="true" />

When to use

Use it when

  • Two to four exclusive options, and seeing all of them helps the reader choose.
  • The choice changes a view — a range, a mode, a theme.
  • The options are short words that fit on one row.

Reach for something else when

  • There are five or more options. → app-select
  • It is a single on/off setting. → app-switch
  • Several can be on at once. → app-checkbox / app-choice-chip
  • The options need descriptions. → app-radio / app-choice-card

Best practices

Do
Short, parallel labels of similar length, so the pills are balanced and the row reads as one control.
Don't
Wildly uneven labels make one pill swamp the other and push the control past its container. If a label needs a sentence, the choice needs radios.

Every option

The whole surface of the component, one cell per value — sizes, icon and note variants, the disabled states, full width, and the edge cases a normal example never shows.

size — sm · md · lg · xl

size="sm"
size="md"
size="lg"
size="xl"

icon + label items

icon="sun" / "moon" / "gauge"

note — under every label

app-segmented-option note

disabled — whole control vs. a single option

disabled="true" — whole control
app-segmented-option disabled="true" — one option

block / full width

full-width="true" — segments share the row equally

edge cases

options — minimum (2)
value matches no option — nothing checked
no app-segmented-option children — empty group

Attributes

AttributeTypeDefaultDescription
label *string—Accessible name for the group. Without it a screen reader announces unrelated radios.
valuestring—The selected option's value.
namestringgeneratedWhat groups the radios and what the value posts as.
sizesm | md | lg | xlmdsm for a card header/toolbar; lg or xl for a more prominent toggle.
full-widthboolfalseSegments share the row equally and fill the container.
borderedbooltrueDraws the primary ring on the selected option. false keeps only the tint and the primary label.
disabledboolfalseDisables the whole control. A single option: <app-segmented-option disabled="true">.

<app-segmented-option> takes value, label, and optional icon, note, and disabled attributes (React option icon / note / disabled) — note renders a smaller, muted sub-line under the label; disabled keeps that one option visible but unpickable.