# ToggleGroup

A segmented control: several options, one strip.

- Group: Forms
- Import: `import { ToggleGroup } from '@misoto22/folio'`
- Page: https://ui.misoto22.com/components/toggle-group/
- Related: tabs, radio-group

## When to reach for it

It changes a VALUE. Something that switches panels is Tabs.

## Anatomy

- **Strip** (required) — The bordered pill holding the segments, w-fit as well as inline-flex — without it a flex or grid parent stretches the strip to the widest sibling and leaves dead space after the last segment.
- **Travelling pill** — The --accent block behind the selection, on single-value groups only and only once it has measured a selected segment. It moves rather than cross-fading, so the eye follows one thing.
- **Segment** (required) — A button at --control-h-sm. In a single group it changes ink only and lets the pill behind it do the filling; in a multiple group it fills itself, because there is nothing travelling.
- **Segment content** — children, in a gap-2 row. There is no iconOnly path here the way Button has one, so an icon with no words is a segment with no name.

## Best practices

### Do

- Give a single-value group a defaultValue or a value. The pill appears only after it has measured a selected segment, so a group that starts empty is a bare strip with nothing marked in it.
- Name the strip. Inside a Field its label does it, through aria-labelledby — the root is a div, so there is nothing for htmlFor to bind to — and standing alone it needs its own aria-label, which with type="single" is what a radiogroup is announced by.
- Give an icon-only segment its own aria-label: nothing strips the text or supplies a name for you here, so a strip of three glyphs announces three unnamed buttons.

### Don’t

- type="single" has radio semantics but not radio behaviour: pressing the selected segment deselects it and commits an empty string, so a view switcher built on it can be switched off into no view at all.
- Segments are --control-h-sm — 36px comfortable, 30px under data-density="compact" — which is below the 44px pointer floor (WCAG 2.5.5). A strip meant for a thumb needs its own height.
- Do not put six options in it. The strip neither wraps nor scrolls, so past about five segments it simply runs out past its container, and that is a Select or a Combobox anyway.
- A Field’s required marks a single-value strip and leaves a multiple-value one unmarked: that one is a role="toolbar", which takes no aria-required at all, so on type="multiple" the asterisk is the whole of the marking.

## Accessibility

- type="single" gets radio semantics; type="multiple" gets independent toggles. Choosing wrong tells a screen reader that picking one option unpicks the others.

## Keyboard

- Tab — Moves into the strip — one stop for the group.
- ← / → — Moves between segments.
- Enter / Space — Toggles the focused segment.

## ToggleGroup

A segmented control: several options, one strip. `type="single"` is a choice — Radix gives it radio semantics, and it is the right shape for a view switcher or a density setting. `type="multiple"` is a set of independent toggles, which is a different thing announced differently; choosing the wrong one is how a "filter by tag" control ends up telling a screen reader that picking one tag unpicks the others. The two look different on purpose. A single-value strip moves ONE filled pill between its options, so the eye follows a thing travelling; a multiple-value strip fills each pressed option separately, because there is no single selection to travel. Two people looking at a screenshot should be able to tell which kind they are looking at, and before this they could not. Distinct from `Tabs`, which switches PANELS and owns a tabpanel relationship. A toggle group changes a value. Inside a `Field` the strip takes its name from that label, by pointing back at it — the root is a div, and `<label for>` does not bind to one, so the words above it do not click through. Standing alone it still needs its own `aria-label`.

Also accepts: `ComponentProps<typeof ToggleGroupPrimitive.Root>`.

## ToggleGroupItem

One segment. In a single-value group the item draws no background of its own — the travelling pill behind it does — so it only changes ink. In a multiple-value group it fills, because there is nothing travelling.

Also accepts: `ComponentProps<typeof ToggleGroupPrimitive.Item>`.

## Example — default

```tsx
import { ToggleGroup, ToggleGroupItem } from '@misoto22/folio'

<div className="flex flex-col gap-8">
  <div className="flex flex-col gap-2">
    <p className="m-0 eyebrow text-(--ink-3-aa)">single — one of these</p>
    <ToggleGroup type="single" defaultValue="grid" aria-label="Layout">
      <ToggleGroupItem value="grid">Grid</ToggleGroupItem>
      <ToggleGroupItem value="list">List</ToggleGroupItem>
      <ToggleGroupItem value="map">Map</ToggleGroupItem>
    </ToggleGroup>
  </div>

  <div className="flex flex-col gap-2">
    <p className="m-0 eyebrow text-(--ink-3-aa)">multiple — any of these</p>
    <ToggleGroup type="multiple" defaultValue={['film']} aria-label="Formats">
      <ToggleGroupItem value="film">Film</ToggleGroupItem>
      <ToggleGroupItem value="digital">Digital</ToggleGroupItem>
      <ToggleGroupItem value="scan">Scan</ToggleGroupItem>
    </ToggleGroup>
  </div>
</div>
```

## Example — icon only

```tsx
import { ToggleGroup, ToggleGroupItem } from '@misoto22/folio'

<ToggleGroup type="single" defaultValue="start" aria-label="Text alignment">
  {ALIGNMENTS.map(({ value, label, icon: Icon }) => (
    <ToggleGroupItem key={value} value={value} aria-label={label} className="px-2.5">
      <Icon size={16} aria-hidden />
    </ToggleGroupItem>
  ))}
</ToggleGroup>
```

## Example — never switched off

```tsx
import { Text, ToggleGroup, ToggleGroupItem } from '@misoto22/folio'

<div className="flex flex-col gap-3">
  <ToggleGroup
    type="single"
    value={view}
    onValueChange={(next) => {
      if (next) setView(next)
    }}
    aria-label="Layout"
  >
    <ToggleGroupItem value="grid">Grid</ToggleGroupItem>
    <ToggleGroupItem value="list">List</ToggleGroupItem>
    <ToggleGroupItem value="map">Map</ToggleGroupItem>
  </ToggleGroup>
  <Text size="sm" tone="muted">
    Showing the {view} view.
  </Text>
</div>
```
