---
id: "packages/active-ui/toggle"
title: "Toggle and ToggleGroup"
description: "A button with an on and an off state, and a group of them that shares one value, for one choice or several."
url: "/packages/active-ui/toggle"
locale: "en"
revision: "16c00d9aaf6fcc60"
---

# Toggle and ToggleGroup

A toggle is a button that stays pressed: bold on or off, notifications muted or not. A toggle
group joins several of them into one value: one alignment out of three, or any combination of
text styles. Use a toggle for a setting that takes effect at once; for a choice that is submitted
with a form, use a checkbox, a switch or a radio group, which are form controls.

**Example: Uncontrolled, controlled and disabled**

Client: Runs entirely in your browser. Source: `examples/ui/toggle.tsx`.

```tsx
// apps/docs/examples/ui/toggle.tsx
'use client';

import {useState} from 'react';
import {BellIcon, BellOffIcon, BoldIcon} from 'lucide-react';

import {Toggle} from '@workspace/active-ui/components/primitives/toggle';

/**
 * A button with an on and an off state. Uncontrolled, it keeps its own
 * state; controlled, `pressed` and `onPressedChange` hand it to your
 * application. Its state is announced as `aria-pressed`.
 */
export default function ToggleExample() {
  const [muted, setMuted] = useState(false);
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Toggle aria-label="Bold">
        <BoldIcon aria-hidden />
      </Toggle>
      <Toggle variant="outline" pressed={muted} onPressedChange={setMuted}>
        {muted ? (
          <BellOffIcon data-icon="inline-start" aria-hidden />
        ) : (
          <BellIcon data-icon="inline-start" aria-hidden />
        )}
        Mute notifications
      </Toggle>
      <p className="text-muted-foreground text-sm" aria-live="polite">
        Notifications are {muted ? 'muted' : 'on'}.
      </p>
      <Toggle disabled aria-label="Bold, not available">
        <BoldIcon aria-hidden />
      </Toggle>
    </div>
  );
}
```

## Usage

```tsx
import {Toggle} from '@workspace/active-ui/components/primitives/toggle';
import {
  ToggleGroup,
  ToggleGroupItem,
} from '@workspace/active-ui/components/primitives/toggle-group';

<Toggle aria-label="Bold">
  <BoldIcon aria-hidden />
</Toggle>;

<ToggleGroup aria-label="Alignment" value={align} onValueChange={setAlign}>
  <ToggleGroupItem value="left">Left</ToggleGroupItem>
  <ToggleGroupItem value="right">Right</ToggleGroupItem>
</ToggleGroup>;
```

## A group of toggles

**Example: One choice and several**

Client: Runs entirely in your browser. Source: `examples/ui/toggle-group.tsx`.

```tsx
// apps/docs/examples/ui/toggle-group.tsx
'use client';

import {useState} from 'react';
import {
  AlignCenterIcon,
  AlignLeftIcon,
  AlignRightIcon,
  BoldIcon,
  ItalicIcon,
  UnderlineIcon,
} from 'lucide-react';

import {
  ToggleGroup,
  ToggleGroupItem,
} from '@workspace/active-ui/components/primitives/toggle-group';

/**
 * Toggles that share one value. By default one item at a time is pressed
 * (alignment); with `multiple` any number (text style). The value is always
 * an array of the `value`s of the pressed items. `spacing={0}` joins the
 * items into one outlined control.
 */
export default function ToggleGroupExample() {
  const [align, setAlign] = useState<string[]>(['left']);
  const [style, setStyle] = useState<string[]>(['bold']);
  return (
    <div className="flex flex-col items-start gap-6">
      <div className="flex flex-col gap-2">
        <ToggleGroup
          aria-label="Alignment"
          variant="outline"
          spacing={0}
          value={align}
          onValueChange={setAlign}
        >
          <ToggleGroupItem value="left" aria-label="Align left">
            <AlignLeftIcon aria-hidden />
          </ToggleGroupItem>
          <ToggleGroupItem value="center" aria-label="Align center">
            <AlignCenterIcon aria-hidden />
          </ToggleGroupItem>
          <ToggleGroupItem value="right" aria-label="Align right">
            <AlignRightIcon aria-hidden />
          </ToggleGroupItem>
        </ToggleGroup>
        <p className="text-muted-foreground text-sm">
          Value: <code>{JSON.stringify(align)}</code>
        </p>
      </div>
      <div className="flex flex-col gap-2">
        <ToggleGroup
          aria-label="Text style"
          multiple
          value={style}
          onValueChange={setStyle}
        >
          <ToggleGroupItem value="bold" aria-label="Bold">
            <BoldIcon aria-hidden />
          </ToggleGroupItem>
          <ToggleGroupItem value="italic" aria-label="Italic">
            <ItalicIcon aria-hidden />
          </ToggleGroupItem>
          <ToggleGroupItem value="underline" aria-label="Underline">
            <UnderlineIcon aria-hidden />
          </ToggleGroupItem>
        </ToggleGroup>
        <p className="text-muted-foreground text-sm">
          Value: <code>{JSON.stringify(style)}</code>
        </p>
      </div>
    </div>
  );
}
```

- The value is always an **array** of the `value`s of the pressed items, also when only one item
  can be pressed. Without `multiple`, pressing an item releases the other; pressing the pressed
  item releases it, so the value can be empty. Refuse an empty value in `onValueChange` when your
  application needs one.
- `variant` and `size` on the group apply to every item.
- `spacing` sets the gap between the items in steps of the spacing scale (default `2`); `0` joins
  them into one control with shared borders, best with `variant="outline"`.
- `orientation="vertical"` stacks the items and makes the arrow keys follow.

## Variants and sizes

- `variant`: `default` (no border, a muted fill when pressed) or `outline` (a border).
- `size`: `sm`, `default` or `lg`, with the same heights as the buttons of those sizes.
- Mark an icon beside text with `data-icon="inline-start"` or `data-icon="inline-end"`, as on a
  [button](/packages/active-ui/button).

## What your application provides

- An accessible name for each icon-only toggle, and for the group.
- The state when it is controlled: `pressed` and `onPressedChange` on a toggle, `value` and
  `onValueChange` on a group.
- What the state changes on the page. A toggle only reports it.

## Accessibility and keyboard

- A toggle is a `<button>` with `aria-pressed`; `Space` and `Enter` switch it.
- In a group, the arrow keys move the focus between the items (left and right, or up and down when
  vertical) and `Tab` leaves the group; `loopFocus` (default `true`) wraps from the last item to the
  first.
- A disabled toggle or group (`disabled`) is skipped and cannot be switched.

## API

### Toggle

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `size` | `"default" \| "lg" \| "sm" \| null` | no | The height and horizontal padding: `sm`, `default` or `lg`, the heights of the buttons of the same size. |
| `variant` | `"default" \| "outline" \| null` | no | The look: `default` has no border, `outline` a border and a light shadow. Pressed, both get the muted fill. |
| `defaultPressed` | `boolean` | no | Whether the toggle button is currently pressed. This is the uncontrolled counterpart of `pressed`. |
| `disabled` | `boolean` | no | Whether the component should ignore user interaction. |
| `nativeButton` | `boolean` | no | 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: Toggle.ChangeEventDetails) => void)` | no | Callback fired when the pressed state is changed. |
| `pressed` | `boolean` | no | Whether the toggle button is currently pressed. This is the controlled counterpart of `defaultPressed`. |
| `render` | `ComponentRenderFn<HTMLProps, ToggleState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | 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. |
| `value` | `string` | no | A unique string that identifies the toggle when used inside a toggle group. |

### ToggleGroup

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `orientation` | `"horizontal" \| "vertical"` | no | The direction of the items, and of the arrow keys that move between them. |
| `size` | `"default" \| "lg" \| "sm" \| null` | no | The height and horizontal padding: `sm`, `default` or `lg`, the heights of the buttons of the same size. |
| `spacing` | `number` | no | The gap between the items, in steps of the spacing scale. `0` joins the items into one control with shared borders. |
| `variant` | `"default" \| "outline" \| null` | no | The look: `default` has no border, `outline` a border and a light shadow. Pressed, both get the muted fill. |
| `defaultValue` | `readonly string[]` | no | 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` | no | Whether the toggle group should ignore user interaction. |
| `loopFocus` | `boolean` | no | 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` | no | 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: ToggleGroup.ChangeEventDetails) => void)` | no | Callback fired when the pressed states of the toggle group changes. |
| `render` | `ComponentRenderFn<HTMLProps, ToggleGroupState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | 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. |
| `value` | `readonly string[]` | no | 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`. |

### ToggleGroupItem

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `size` | `"default" \| "lg" \| "sm" \| null` | no | The height and horizontal padding: `sm`, `default` or `lg`, the heights of the buttons of the same size. |
| `variant` | `"default" \| "outline" \| null` | no | The look: `default` has no border, `outline` a border and a light shadow. Pressed, both get the muted fill. |
| `defaultPressed` | `boolean` | no | Whether the toggle button is currently pressed. This is the uncontrolled counterpart of `pressed`. |
| `disabled` | `boolean` | no | Whether the component should ignore user interaction. |
| `nativeButton` | `boolean` | no | 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: Toggle.ChangeEventDetails) => void)` | no | Callback fired when the pressed state is changed. |
| `pressed` | `boolean` | no | Whether the toggle button is currently pressed. This is the controlled counterpart of `defaultPressed`. |
| `render` | `ComponentRenderFn<HTMLProps, ToggleState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | 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. |
| `value` | `string` | no | A unique string that identifies the toggle when used inside a toggle group. |

### Other exports

**toggleVariants**: `(props?: (ConfigVariants<{ variant: { default: string; outline: string; }; size: { default: string; sm: string; lg: string; }; }> & ClassProp) \| undefined) => string`

> [!NOTE]
> **Based on shadcn/ui**
>
> This page adapts the Toggle and Toggle Group documentation of shadcn/ui (MIT
> licence) to this package; see [Third-party notices](/third-party-notices).
