# Toggle group

A row of toggles that share one choice, or several, such as a period or a set of statuses.

- **Import:** `import { ToggleGroup, ToggleGroupItem } from "@booleanpress/ui/toggle-group"`
- **Radix Toggle Group:** <https://www.radix-ui.com/primitives/docs/components/toggle-group>
- **APG Radio Group (single):** <https://www.w3.org/WAI/ARIA/apg/patterns/radio/>
- **Page:** <https://ui.booleanpress.com/components/toggle-group> · @booleanpress/ui 0.1.0

## Usage

`type` is required: `"single"` allows one pressed item at a time, `"multiple"` any number. Name the group with `aria-label`, and name icon-only items with `aria-label` too.

```tsx
import { ToggleGroup, ToggleGroupItem } from "@booleanpress/ui/toggle-group"

export function Period() {
  return (
    <ToggleGroup type="single" variant="outline" defaultValue="7d" aria-label="Period">
      <ToggleGroupItem value="24h">24 hours</ToggleGroupItem>
      <ToggleGroupItem value="7d">7 days</ToggleGroupItem>
      <ToggleGroupItem value="30d">30 days</ToggleGroupItem>
    </ToggleGroup>
  )
}
```

With `type="single"`, `value` is a string and pressing the pressed item clears it to `""`; guard in `onValueChange` when a choice is required. With `type="multiple"`, `value` is an array of strings. Both are controlled with `value` and `onValueChange`, or uncontrolled with `defaultValue`. `variant` and `size` are set once on the group and reach every item. `spacing` is the gap between items in 4 px steps: `0` (the default) joins the items into one bar; any larger number separates them. For a choice that submits with a form, use `RadioGroup`.

## Examples

### Single

One item pressed at a time, as a period filter.

```tsx
import { ToggleGroup, ToggleGroupItem } from "@booleanpress/ui/toggle-group"

export default function ToggleGroupSingle() {
  return (
    <ToggleGroup type="single" variant="outline" defaultValue="7d" aria-label="Period">
      <ToggleGroupItem value="24h">24 hours</ToggleGroupItem>
      <ToggleGroupItem value="7d">7 days</ToggleGroupItem>
      <ToggleGroupItem value="30d">30 days</ToggleGroupItem>
    </ToggleGroup>
  )
}
```

### Multiple

Any number of items pressed, as a set of status filters.

```tsx
import { ToggleGroup, ToggleGroupItem } from "@booleanpress/ui/toggle-group"

export default function ToggleGroupMultiple() {
  return (
    <ToggleGroup type="multiple" variant="outline" defaultValue={["bounced", "failed"]} aria-label="Show statuses">
      <ToggleGroupItem value="delivered">Delivered</ToggleGroupItem>
      <ToggleGroupItem value="bounced">Bounced</ToggleGroupItem>
      <ToggleGroupItem value="failed">Failed</ToggleGroupItem>
    </ToggleGroup>
  )
}
```

### Spacing and size

`spacing` separates the items into their own buttons; `size` is set on the group.

```tsx
import { ToggleGroup, ToggleGroupItem } from "@booleanpress/ui/toggle-group"

export default function ToggleGroupSpacing() {
  return (
    <div className="flex flex-col gap-4">
      <ToggleGroup type="single" variant="outline" size="sm" spacing={2} defaultValue="all" aria-label="Filter, spaced">
        <ToggleGroupItem value="all">All</ToggleGroupItem>
        <ToggleGroupItem value="open">Open</ToggleGroupItem>
        <ToggleGroupItem value="closed">Closed</ToggleGroupItem>
      </ToggleGroup>
      <ToggleGroup type="single" size="lg" defaultValue="all" aria-label="Filter, large">
        <ToggleGroupItem value="all">All</ToggleGroupItem>
        <ToggleGroupItem value="open">Open</ToggleGroupItem>
        <ToggleGroupItem value="closed">Closed</ToggleGroupItem>
      </ToggleGroup>
    </div>
  )
}
```

### Disabled

`disabled` on the group stops every item; on one item it stops that item and the arrow keys skip it.

```tsx
import { ToggleGroup, ToggleGroupItem } from "@booleanpress/ui/toggle-group"

export default function ToggleGroupDisabled() {
  return (
    <div className="flex flex-col gap-4">
      <ToggleGroup type="single" variant="outline" disabled defaultValue="list" aria-label="View, group disabled">
        <ToggleGroupItem value="list">List</ToggleGroupItem>
        <ToggleGroupItem value="board">Board</ToggleGroupItem>
      </ToggleGroup>
      <ToggleGroup type="single" variant="outline" defaultValue="list" aria-label="View, one item disabled">
        <ToggleGroupItem value="list">List</ToggleGroupItem>
        <ToggleGroupItem value="board">Board</ToggleGroupItem>
        <ToggleGroupItem value="timeline" disabled>
          Timeline
        </ToggleGroupItem>
      </ToggleGroup>
    </div>
  )
}
```

## Accessibility

**Semantics.** With `type="single"`, the group is `role="radiogroup"` and each item is `role="radio"` with `aria-checked`. With `type="multiple"`, the group is `role="group"` and each item is a button with `aria-pressed`.

**Labels.** Name the group with `aria-label` or `aria-labelledby`. Name icon-only items with `aria-label`.

**Focus.** The group is one tab stop: Tab goes to the pressed item, or to the first when none is pressed, and the other items are skipped. The arrow keys move focus without changing the value.

**Known limits.**

- With `type="single"`, an item reads as a radio, yet pressing it again clears the group: unlike a radio group, "nothing chosen" is reachable.
- Each item is 32 to 40 px high, depending on `size`, and at least as wide as its text.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Tab | Moves focus into the group, to the pressed item, or to the first item when none is pressed. A second Tab leaves the group. |
| ArrowRight + ArrowDown | Moves focus to the next item, wrapping from the last to the first. In right-to-left, ArrowLeft moves forward instead of ArrowRight. It does not change the value. |
| ArrowLeft + ArrowUp | Moves focus to the previous item, wrapping from the first to the last. In right-to-left, ArrowRight moves back instead of ArrowLeft. It does not change the value. |
| Home | Moves focus to the first item. |
| End | Moves focus to the last item. |
| Space + Enter | Presses the focused item. In a single group it releases the pressed item first; pressing the pressed item clears the group. |

## API

### ToggleGroup

Renders Radix ToggleGroup.Root and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `type` (required) | `"single" \| "multiple"` |  | `"single"` allows one pressed item; `"multiple"` allows any number. Required. |
| `asChild` | `boolean` |  | Render the child element instead, with this part's behaviour and classes merged onto it. |
| `defaultValue` | `string \| string[]` |  | The value of the item that is pressed when initially rendered. Use `defaultValue` if you do not need to control the state of a toggle group. The value of the items that are pressed when initially rendered. Use `defaultValue` if you do not need to control the state of a toggle group. |
| `dir` | `"ltr" \| "rtl"` |  | Reading direction for the arrow keys. Defaults to the provider's. |
| `disabled` | `boolean` |  | Whether the group is disabled from user interaction. |
| `loop` | `boolean` |  | Whether the arrow keys wrap around at the ends. Defaults to `true`. |
| `onValueChange` | `((value: string) => void) \| ((value: string[]) => void)` |  | The callback that fires when the value of the toggle group changes. The callback that fires when the state of the toggle group changes. |
| `orientation` | `"horizontal" \| "vertical"` |  | `horizontal` or `vertical`, for the arrow keys. It does not change the layout. |
| `rovingFocus` | `boolean` |  | Whether the group should maintain roving focus of its buttons. |
| `spacing` | `number` | `0` | The gap between items in 4 px steps. `0` joins them into one bar. |
| `value` | `string \| string[]` |  | The controlled stateful value of the item that is pressed. The controlled stateful value of the items that are pressed. |

### ToggleGroupItem

Renders Radix ToggleGroup.Item and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  | Render the child element instead, with this part's behaviour and classes merged onto it. |
| `value` | `string \| string[]` |  | The controlled stateful value of the item that is pressed. The controlled stateful value of the items that are pressed. |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="toggle-group"` (ToggleGroup), `data-slot="toggle-group-item"` (ToggleGroupItem), and `data-variant`, `data-size`, `data-spacing`.

## Theming

Items share the toggle's tokens: `--accent` when pressed, `--muted` on hover, `--input` for the `outline` border. With `spacing={0}` the outer corners are rounded and the inner ones square, mirrored in right-to-left.
