# Segmented buttons

> Two to five connected options, one choice or several, with no script behind them.

## Anatomy

A segmented button is a small set of related options shown side by side inside one outlined pill — a view switcher, a date range, a filter over a chart. Two to five segments; beyond that use a [select](/select.html.md) or [chips](/chips.html.md).

The root is a `<fieldset class="segmented-button">` with a `<legend>`. Each segment is an `<input>` and the `<label class="segment">` beside it, tied together by `id` and `for`. That is the whole component: there is no plugin, nothing to initialize, and no selected class to keep in sync. The input holds the state and the form value, and the browser moves it.

The control is the label's sibling rather than its child, the same way a [filter chip](/chips.html.md) is written. A `<label>` wrapping a radio or a checkbox *is* one as far as the rest of the framework is concerned, and would be painted as one — a 20dp ring on the control and a 48dp row around it.

The legend names the group for assistive technology and is not shown — Material 3's anatomy has no visible group label, but a set of radios is only a set because a fieldset says so, and a fieldset is only named by its legend.

There is no modifier that shows it, and unhiding it by hand is not one override but six. The root is both the fieldset and the grid of segments, so a legend back in flow becomes another column beside them. When the group needs a visible label, write one before the fieldset — a heading or a `<p>` — and leave the legend saying the same thing as the group's name.

View  Day  Week  Month

## Single select

Radios sharing a `name`. Exactly one segment is chosen, the arrow keys move between them and Tab enters and leaves the group as one stop — all of it the browser's, because the control is a real radio group. Mark the initial choice with `checked`.

```
<fieldset class="segmented-button">
  <legend>View</legend>
  <input type="radio" id="view-day" name="view" value="day" checked>
  <label class="segment" for="view-day">Day</label>
  <input type="radio" id="view-week" name="view" value="week">
  <label class="segment" for="view-week">Week</label>
  <input type="radio" id="view-month" name="view" value="month">
  <label class="segment" for="view-month">Month</label>
</fieldset>
```

## Multi select

Checkboxes instead. Any number of segments can be on at once, each one its own Tab stop and its own form value. Nothing else changes — the input type is the entire difference between the two variants.

Filter transport  Walk  Transit  Bike

```
<fieldset class="segmented-button">
  <legend>Filter transport</legend>
  <input type="checkbox" id="transport-walk" name="transport" value="walk" checked>
  <label class="segment" for="transport-walk">Walk</label>
  <input type="checkbox" id="transport-transit" name="transport" value="transit" checked>
  <label class="segment" for="transport-transit">Transit</label>
  <input type="checkbox" id="transport-bike" name="transport" value="bike">
  <label class="segment" for="transport-bike">Bike</label>
</fieldset>
```

## With icons

Put a `<span class="material-symbols">` in the label, before or after the text, at 18dp. The segment's text is its accessible name, so the icon is decoration and is `aria-hidden`. Material 3 shows a check on the chosen segment; write that icon yourself if you want it — nothing swaps it in, and the filled container already says which one is on.

Map layer  Map  Satellite

```
<fieldset class="segmented-button">
  <legend>Map layer</legend>
  <input type="radio" id="layer-map" name="layer" value="map" checked>
  <label class="segment" for="layer-map">
    <span class="material-symbols" aria-hidden="true">map</span>
    Map
  </label>
  <input type="radio" id="layer-satellite" name="layer" value="satellite">
  <label class="segment" for="layer-satellite">
    <span class="material-symbols" aria-hidden="true">satellite_alt</span>
    Satellite
  </label>
</fieldset>
```

## Disabled

`disabled` on one input greys that segment; on the fieldset it greys the whole group, outline included, the way any fieldset disables what it contains.

Density  Comfortable  Compact

Sort  Newest  Oldest

```
<fieldset class="segmented-button" disabled>
  <legend>Sort</legend>
  <input type="radio" id="sort-newest" name="sort" value="newest" checked>
  <label class="segment" for="sort-newest">Newest</label>
  <input type="radio" id="sort-oldest" name="sort" value="oldest">
  <label class="segment" for="sort-oldest">Oldest</label>
</fieldset>
```

## Semantics

A [composite role](https://github.com/BaezFJ/ExpressiveCSS/blob/master/SEMANTICS.md) such as `radiogroup` promises arrow-key navigation, and this component *rejects* it rather than withholding it: a fieldset of radios already is one, with the keyboard model implemented by the browser. Writing the role by hand would restate what the element already says and take away the group role the fieldset gives you. No composite role is accepted on the group at all.

What that costs is that the markup has to stay native, so the docs suite enforces it — the root must be a `<fieldset>`, every segment a `<label for>` against a real `<input>`, and every radio must carry the shared `name` that makes it a group, since that is where the arrow keys come from. `aria-checked` and its neighbours are refused: the input's `checked` state is the answer, and a hand-written copy is a second one that nothing updates.

## Tokens

Set these on the group itself, or in a rule of your own that picks out the groups you want to change — `.chart-controls .segmented-button { … }`. They are declared on the component, so a value set on an ancestor *element* is shadowed by the group's own default rather than inherited.

The group is a grid of equal columns filling the width it is given, which is what Material specifies — constrain it with a width or a wrapper when it should be narrower than its container.

| Token | Default |
| --- | --- |
| `--md-comp-outlined-segmented-button-container-height` | `40px` |
| `--md-comp-outlined-segmented-button-container-shape` | `9999px` |
| `--md-comp-outlined-segmented-button-outline-width` | `1px` |
| `--md-comp-outlined-segmented-button-outline-color` | `var(--md-sys-color-outline)` |
| `--md-comp-outlined-segmented-button-leading-space` | `12px` |
| `--md-comp-outlined-segmented-button-trailing-space` | `12px` |
| `--md-comp-outlined-segmented-button-icon-size` | `18px` |
| `--md-comp-outlined-segmented-button-icon-label-space` | `8px` |
| `--md-comp-outlined-segmented-button-label-text-color` | `var(--md-sys-color-on-surface)` |
| `--md-comp-outlined-segmented-button-selected-container-color` | `var(--md-sys-color-secondary-container)` |
| `--md-comp-outlined-segmented-button-selected-label-text-color` | `var(--md-sys-color-on-secondary-container)` |

The state layer is the label colour mixed into the container at the [state layer](/state-layers.html.md) opacity, so those two colour tokens carry hover and press with them.
