# Toggle Group (/ui/components/react/toggle-group)



## Usage [#usage]

```tsx
import { ToggleGroup } from '@appica/ui-react/toggle-group'
import { Toggle } from '@appica/ui-react/toggle'
import { Button } from '@appica/ui-react/button'
```

```tsx
<ToggleGroup aria-label="Text alignment" defaultValue={['center']}>
  <Toggle
    value="left"
    aria-label="Align left"
    render={
      <Button variant="ghost" size="icon-md">
        <AlignLeft />
      </Button>
    }
  />
  <Toggle
    value="center"
    aria-label="Align center"
    render={
      <Button variant="ghost" size="icon-md">
        <AlignCenter />
      </Button>
    }
  />
  <Toggle
    value="right"
    aria-label="Align right"
    render={
      <Button variant="ghost" size="icon-md">
        <AlignRight />
      </Button>
    }
  />
</ToggleGroup>
```

`ToggleGroup` lays out a row of [`Toggle`](/ui/components/react/toggle)s and tracks which ones are pressed as a string array of their `value`s. It's single-select by default — pressing one toggle releases the previous — and becomes multi-select with the `multiple` prop, where any number of toggles can stay pressed at once. Drive it uncontrolled with `defaultValue`, or controlled with `value` + `onValueChange`.

`Toggle` ships no styling of its own, so give each one a look by composing it with [`Button`](/ui/components/react/button) through `render={<Button … />}` — `Button` already reacts to `data-pressed`, so the pressed state comes for free. Because a toggle group is a `role="group"` rather than a labelable form control, name it with `aria-label` or `aria-labelledby` — never with a `<Field>`/`FieldLabel`.

## Examples [#examples]

### Single selection [#single-selection]

By default the group is single-select: pressing a toggle releases whichever one was pressed before, so exactly one stays active.

```tsx
import { Button } from '@appica/ui-react/button'
import { Toggle } from '@appica/ui-react/toggle'
import { ToggleGroup } from '@appica/ui-react/toggle-group'
import { AlignLeft, AlignCenter, AlignRight } from '@appica/icons-react'

export default function ToggleGroupSingle() {
  return (
    <ToggleGroup aria-label="Text alignment" defaultValue={['center']}>
      <Toggle
        value="left"
        aria-label="Align left"
        render={
          <Button variant="ghost" size="icon-md">
            <AlignLeft />
          </Button>
        }
      />
      <Toggle
        value="center"
        aria-label="Align center"
        render={
          <Button variant="ghost" size="icon-md">
            <AlignCenter />
          </Button>
        }
      />
      <Toggle
        value="right"
        aria-label="Align right"
        render={
          <Button variant="ghost" size="icon-md">
            <AlignRight />
          </Button>
        }
      />
    </ToggleGroup>
  )
}
```

### Multiple selection [#multiple-selection]

Add `multiple` to let several toggles stay pressed at once — handy for independent formatting options like bold, italic, and underline.

```tsx
import { Button } from '@appica/ui-react/button'
import { Toggle } from '@appica/ui-react/toggle'
import { ToggleGroup } from '@appica/ui-react/toggle-group'
import { Bold, Italic, Underline } from '@appica/icons-react'

export default function ToggleGroupMultiple() {
  return (
    <ToggleGroup aria-label="Text formatting" multiple defaultValue={['bold']}>
      <Toggle
        value="bold"
        aria-label="Bold"
        render={
          <Button variant="ghost" size="icon-md">
            <Bold />
          </Button>
        }
      />
      <Toggle
        value="italic"
        aria-label="Italic"
        render={
          <Button variant="ghost" size="icon-md">
            <Italic />
          </Button>
        }
      />
      <Toggle
        value="underline"
        aria-label="Underline"
        render={
          <Button variant="ghost" size="icon-md">
            <Underline />
          </Button>
        }
      />
    </ToggleGroup>
  )
}
```

### Vertical orientation [#vertical-orientation]

Set `orientation="vertical"` to stack the toggles in a column. Arrow-key focus follows the same axis.

```tsx
import { Button } from '@appica/ui-react/button'
import { Toggle } from '@appica/ui-react/toggle'
import { ToggleGroup } from '@appica/ui-react/toggle-group'
import { AlignLeft, AlignCenter, AlignRight } from '@appica/icons-react'

export default function ToggleGroupVertical() {
  return (
    <ToggleGroup aria-label="Text alignment" orientation="vertical" defaultValue={['left']}>
      <Toggle
        value="left"
        aria-label="Align left"
        render={
          <Button variant="ghost" size="icon-md">
            <AlignLeft />
          </Button>
        }
      />
      <Toggle
        value="center"
        aria-label="Align center"
        render={
          <Button variant="ghost" size="icon-md">
            <AlignCenter />
          </Button>
        }
      />
      <Toggle
        value="right"
        aria-label="Align right"
        render={
          <Button variant="ghost" size="icon-md">
            <AlignRight />
          </Button>
        }
      />
    </ToggleGroup>
  )
}
```

### Disabled [#disabled]

Set `disabled` on the group to disable every toggle inside it at once.

```tsx
import { Button } from '@appica/ui-react/button'
import { Toggle } from '@appica/ui-react/toggle'
import { ToggleGroup } from '@appica/ui-react/toggle-group'
import { AlignLeft, AlignCenter, AlignRight } from '@appica/icons-react'

export default function ToggleGroupDisabled() {
  return (
    <ToggleGroup aria-label="Text alignment" disabled defaultValue={['center']}>
      <Toggle
        value="left"
        aria-label="Align left"
        render={
          <Button variant="ghost" size="icon-md">
            <AlignLeft />
          </Button>
        }
      />
      <Toggle
        value="center"
        aria-label="Align center"
        render={
          <Button variant="ghost" size="icon-md">
            <AlignCenter />
          </Button>
        }
      />
      <Toggle
        value="right"
        aria-label="Align right"
        render={
          <Button variant="ghost" size="icon-md">
            <AlignRight />
          </Button>
        }
      />
    </ToggleGroup>
  )
}
```

### Color swatches (controlled) [#color-swatches-controlled]

A controlled single-select group, used as a color picker. Here the toggles aren't button-shaped, so they're styled directly rather than composed with `Button` — each bare `Toggle` is a 16px circular swatch carrying its color, and the pressed one gets an offset `border-inverse` ring. Driving it with `value` + `onValueChange` keeps the selection in state, which the label above reads to name the current color.

```tsx
'use client'

import * as React from 'react'
import { Toggle } from '@appica/ui-react/toggle'
import { ToggleGroup } from '@appica/ui-react/toggle-group'

const SWATCHES = [
  { value: 'sage', name: 'Sage', color: '#8fb478' },
  { value: 'terracotta', name: 'Terracotta', color: '#cc8259' },
  { value: 'sand', name: 'Sand', color: '#d9ba6e' },
  { value: 'blush', name: 'Blush', color: '#db9385' },
  { value: 'denim', name: 'Denim', color: '#7e9cc9' },
]

export default function ToggleGroupSwatches() {
  const [value, setValue] = React.useState(['sage'])
  const selected = SWATCHES.find((swatch) => swatch.value === value[0])

  return (
    <div className="flex flex-col items-start gap-3">
      <span className="text-foreground-muted text-sm">
        Color - <span className="text-foreground-intense font-medium">{selected?.name}</span>
      </span>
      <ToggleGroup
        aria-label="Swatch color"
        className="gap-3"
        value={value}
        onValueChange={(next) => next.length > 0 && setValue(next)}
      >
        {SWATCHES.map((swatch) => (
          <Toggle
            key={swatch.value}
            value={swatch.value}
            aria-label={swatch.name}
            style={{ backgroundColor: swatch.color }}
            className="after:ring-border-inverse outline-ring relative size-4 rounded-full outline-offset-2 transition-transform after:pointer-events-none after:absolute after:-inset-0.5 after:scale-50 after:rounded-full after:opacity-0 after:ring-1 after:transition after:duration-200 after:ease-[cubic-bezier(0.175,0.885,0.32,1.5)] active:scale-90 data-pressed:after:scale-100 data-pressed:after:opacity-100 motion-reduce:transition-none motion-reduce:after:transition-none"
          />
        ))}
      </ToggleGroup>
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and the like — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The toggles align to the start edge and arrow-key focus follows the resolved direction, so the first toggle sits on the right under RTL. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="toggle-group" />

## API reference [#api-reference]

`ToggleGroup` wraps [Base UI's Toggle Group](https://base-ui.com/react/components/toggle-group), renders with `role="group"`, and forwards `ref` and every remaining `<div>` attribute. Its children are [`Toggle`](/ui/components/react/toggle)s — each needs a `value` and an accessible name. To present a standalone toolbar, pass `role="toolbar"` yourself (don't nest one inside [`Toolbar`](/ui/components/react/toolbar), which is already a toolbar).

| Prop            | <ColMinWidth width="200">Type</ColMinWidth>                   | Default        | <ColMinWidth width="220">Description</ColMinWidth>                                                             |
| --------------- | ------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------- |
| `value`         | `string[]`                                                    | —              | The pressed toggles' `value`s, for controlled use. Pair with `onValueChange`.                                  |
| `defaultValue`  | `string[]`                                                    | —              | The initially pressed toggles, for uncontrolled use.                                                           |
| `onValueChange` | <Code>(value: string\[], eventDetails) => void</Code>         | —              | Called when the pressed set changes, with the new array of `value`s.                                           |
| `multiple`      | `boolean`                                                     | `false`        | Allow several toggles to be pressed at once. When `false`, pressing one releases the previous (single-select). |
| `orientation`   | <Code>'horizontal' \| 'vertical'</Code>                       | `'horizontal'` | Layout axis and the direction arrow keys move focus. Exposed as `data-orientation`.                            |
| `loopFocus`     | `boolean`                                                     | `true`         | Whether arrow-key focus wraps around from the last toggle to the first and back.                               |
| `disabled`      | `boolean`                                                     | `false`        | Disable every toggle in the group.                                                                             |
| `render`        | <Code>ReactElement \| ((props, state) => ReactElement)</Code> | —              | Replace the rendered element, or compose it with another component.                                            |
| `className`     | <Code>string \| ((state) => string)</Code>                    | —              | Extra classes, merged via `tailwind-merge`. May be a function of state.                                        |

## Accessibility [#accessibility]

* The group renders as a `role="group"`, so name it with `aria-label` or `aria-labelledby` — not a `<Field>`/`FieldLabel`, whose `<label for>` doesn't associate with a group.
* Arrow keys move focus between toggles via roving tabindex (the row has a single tab stop), following the group's `orientation`; `loopFocus` controls whether focus wraps at the ends.
* Space or Enter toggles the focused button, which exposes its pressed state through `aria-pressed`.
* Setting `disabled` on the group disables all of its toggles, removing them from the tab order.
