# Toggle Group

A set of toggle buttons that share one state, for picking one option or several from a short list.

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

function ToggleGroupDemo() {
  return (
    <ToggleGroup
      variant="segmented"
      size="sm"
      defaultValue={["7d"]}
      aria-label="Time range"
    >
      <ToggleGroupItem value="24h">24h</ToggleGroupItem>
      <ToggleGroupItem value="7d">7 days</ToggleGroupItem>
      <ToggleGroupItem value="30d">30 days</ToggleGroupItem>
      <ToggleGroupItem value="90d">90 days</ToggleGroupItem>
    </ToggleGroup>
  )
}
```

## Usage

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

export function TimeRange() {
  return (
    <ToggleGroup
      variant="segmented"
      defaultValue={["7d"]}
      aria-label="Time range"
    >
      <ToggleGroupItem value="24h">24h</ToggleGroupItem>
      <ToggleGroupItem value="7d">7 days</ToggleGroupItem>
      <ToggleGroupItem value="30d">30 days</ToggleGroupItem>
    </ToggleGroup>
  )
}
```

Use a toggle group for two to five short options that change what the current view shows: a
time range, a layout, a set of severities. The value is always an array of item `value`s,
even when only one item can be pressed.

Use something else when:

- The options are long, many, or need a description each: use a
  [Radio Group](/components/radio-group) or a [Select](/components/select).
- Each option switches to a different panel of content: use [Tabs](/components/tabs).
- There is a single on/off option: use a [Toggle](/components/toggle).

Set `variant` and `size` on `ToggleGroup`; every item inherits them.

| Variant     | Use for                                                                                               |
| ----------- | ----------------------------------------------------------------------------------------------------- |
| `segmented` | A segmented control: one choice from a few, inside one bordered track. One item always stays pressed. |
| `outline`   | Filters and layout switches in toolbars, where items read as buttons.                                 |
| `default`   | Icon toolbars on a plain surface, with no borders.                                                    |

## Examples

### Multiple selection

Set `multiple` to let the user press any number of items. Here the group filters alerts by
severity.

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

function ToggleGroupMultiple() {
  return (
    <ToggleGroup
      multiple
      variant="outline"
      size="sm"
      defaultValue={["critical", "high"]}
      aria-label="Severity"
    >
      <ToggleGroupItem value="critical">Critical</ToggleGroupItem>
      <ToggleGroupItem value="high">High</ToggleGroupItem>
      <ToggleGroupItem value="medium">Medium</ToggleGroupItem>
      <ToggleGroupItem value="low">Low</ToggleGroupItem>
    </ToggleGroup>
  )
}
```

### Joined

`spacing={0}` removes the gap and merges the borders, so the items read as one control. Every
icon-only item needs an `aria-label`.

```tsx
import { LayoutGridIcon, ListIcon, TableIcon } from "lucide-react"
import { ToggleGroup, ToggleGroupItem } from "@zenoxai/ui/components/toggle-group"

function ToggleGroupJoined() {
  return (
    <ToggleGroup
      variant="outline"
      spacing={0}
      defaultValue={["table"]}
      aria-label="Layout"
    >
      <ToggleGroupItem value="table" aria-label="Table">
        <TableIcon />
      </ToggleGroupItem>
      <ToggleGroupItem value="list" aria-label="List">
        <ListIcon />
      </ToggleGroupItem>
      <ToggleGroupItem value="grid" aria-label="Grid">
        <LayoutGridIcon />
      </ToggleGroupItem>
    </ToggleGroup>
  )
}
```

### Sizes

`sm` (28px) for filter bars and card headers, `default` (32px) for page toolbars, `lg` (36px)
for prominent controls. The whole control is as tall as a [Button](/components/button) of the
same size, segmented track included, so it lines up with the buttons and inputs beside it.

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

function ToggleGroupSizes() {
  return (
    <div className="flex flex-col items-center gap-3">
      <ToggleGroup
        variant="segmented"
        size="sm"
        defaultValue={["open"]}
        aria-label="Status, small"
      >
        <ToggleGroupItem value="open">Open</ToggleGroupItem>
        <ToggleGroupItem value="closed">Closed</ToggleGroupItem>
      </ToggleGroup>
      <ToggleGroup
        variant="segmented"
        defaultValue={["open"]}
        aria-label="Status, default"
      >
        <ToggleGroupItem value="open">Open</ToggleGroupItem>
        <ToggleGroupItem value="closed">Closed</ToggleGroupItem>
      </ToggleGroup>
      <ToggleGroup
        variant="segmented"
        size="lg"
        defaultValue={["open"]}
        aria-label="Status, large"
      >
        <ToggleGroupItem value="open">Open</ToggleGroupItem>
        <ToggleGroupItem value="closed">Closed</ToggleGroupItem>
      </ToggleGroup>
    </div>
  )
}
```

### Controlled

A segmented group without `multiple` always keeps one item pressed: clicking the pressed item
does nothing. In the other variants the pressed item can be unpressed, which leaves the value
empty; when the view needs a value at all times, control the group and ignore empty changes in
`onValueChange`.

```tsx
import * as React from "react"
import { ToggleGroup, ToggleGroupItem } from "@zenoxai/ui/components/toggle-group"

function ToggleGroupControlled() {
  const [status, setStatus] = React.useState(["open"])

  return (
    <div className="flex flex-col items-center gap-3">
      <ToggleGroup
        variant="segmented"
        size="sm"
        value={status}
        onValueChange={setStatus}
        aria-label="Alert status"
      >
        <ToggleGroupItem value="open">Open</ToggleGroupItem>
        <ToggleGroupItem value="acknowledged">Acknowledged</ToggleGroupItem>
        <ToggleGroupItem value="resolved">Resolved</ToggleGroupItem>
      </ToggleGroup>
      <p className="text-sm text-text-secondary">Showing {status[0]} alerts.</p>
    </div>
  )
}
```

## Accessibility

- The group renders `role="group"` and every item is a `<button>` with `aria-pressed`. Name the
  group with `aria-label` so screen readers announce what the options are for.
- The group is one tab stop. The Left and Right arrow keys move between items (wrapping at the
  ends unless `loopFocus={false}`), Home and End jump to the first and last, and Space or Enter
  presses the focused item.

## API reference

`import { ... } from "@zenoxai/ui/components/toggle-group"`

### ToggleGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| defaultValue | `readonly string[]` |  | The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the uncontrolled counterpart of `value`. |
| disabled | `boolean` | `false` | Whether the toggle group should ignore user interaction. |
| loopFocus | `boolean` | `true` | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. |
| multiple | `boolean` | `false` | When `false` only one item in the group can be pressed. If any item in the group becomes pressed, the others will become unpressed. When `true` multiple items can be pressed. |
| onValueChange | `((groupValue: string[], eventDetails: { reason: "none"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element; }) => void)` |  | Callback fired when the pressed states of the toggle group changes. |
| orientation | `"horizontal" \| "vertical"` | `horizontal` |  |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ToggleGroupState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| size | `"default" \| "sm" \| "lg" \| null` |  | Height: `sm` 28px, `default` 32px, `lg` 36px. |
| spacing | `number` | `0` | Gap between items, in 4px steps. 0 (default) joins them into one bar. Ignored by the segmented variant, which has its own track. |
| value | `readonly string[]` |  | The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the controlled counterpart of `defaultValue`. |
| variant | `"default" \| "outline" \| "segmented" \| null` |  | `default` is borderless; `outline` adds the border of an outline Button; `segmented` is an item of a segmented ToggleGroup. Every variant marks the pressed state the same way, with the selection colors (primary-soft fill and text) that FilterChip and the sidebar use, so pressed never looks like hover. |

### ToggleGroupItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| defaultPressed | `boolean` | `false` | Whether the toggle button is currently pressed. This is the uncontrolled counterpart of `pressed`. |
| disabled | `boolean` | `false` | Whether the component should ignore user interaction. |
| nativeButton | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| onPressedChange | `((pressed: boolean, eventDetails: { reason: "none"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element; }) => void)` |  | Callback fired when the pressed state is changed. |
| pressed | `boolean` |  | Whether the toggle button is currently pressed. This is the controlled counterpart of `defaultPressed`. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ToggleState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| size | `"default" \| "sm" \| "lg" \| null` | `default` | Height: `sm` 28px, `default` 32px, `lg` 36px. |
| value | `string` |  | A unique string that identifies the toggle when used inside a toggle group. |
| variant | `"default" \| "outline" \| "segmented" \| null` | `default` | `default` is borderless; `outline` adds the border of an outline Button; `segmented` is an item of a segmented ToggleGroup. Every variant marks the pressed state the same way, with the selection colors (primary-soft fill and text) that FilterChip and the sidebar use, so pressed never looks like hover. |

