# Toggle Group

A set of two-state buttons that can be toggled on or off.

```tsx
import { Bold, Italic, Underline } from "lucide-react"

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

export default function ToggleGroupDemo() {
  return (
    <ToggleGroup aria-label="Formatting" variant="outline" multiple>
      <ToggleGroupItem value="bold" aria-label="Toggle bold">
        <Bold />
      </ToggleGroupItem>
      <ToggleGroupItem value="italic" aria-label="Toggle italic">
        <Italic />
      </ToggleGroupItem>
      <ToggleGroupItem value="underline" aria-label="Toggle underline">
        <Underline />
      </ToggleGroupItem>
    </ToggleGroup>
  )
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex
   ```

2. Set up the token layer and the StyleX compiler. See [Installation](https://carbon.style/docs/installation)
   for `states.ts`, `state-styles.ts` and `interop.css`, which sizes icons.
   Install [Toggle](https://carbon.style/docs/components/toggle) and the Button it depends on. Toggle
   provides the items and the shared group contexts.

3. Copy and paste the following code into your project.

   ```tsx title="components/ui/toggle-group.tsx"
   "use client"

   import { use, type CSSProperties } from "react"
   import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"

   import { ButtonGroupContext } from "@/components/ui/button"
   import {
     Toggle,
     ToggleGroupContext,
     type ToggleProps,
     type ToggleSize,
     type ToggleVariant,
   } from "@/components/ui/toggle"
   import { stateStyles } from "@/lib/state-styles"
   import { states } from "@/theme/states"
   import { color, radius, space } from "@/theme/tokens.stylex"

   const styles = stylex.create({
     root: { display: "flex", width: "fit-content", alignItems: "stretch" },
     vertical: { flexDirection: "column" },
     segmented: {
       "--carbon-toggle-group-radius": radius.lg,
       borderRadius: "var(--carbon-toggle-group-radius)",
       padding: "3px",
       color: color.mutedForeground,
     },
   })
   const spacingStyles = stylex.create({
     0: { gap: space.s0 },
     0.5: { gap: space.s0_5 },
     1: { gap: space.s1 },
     1.5: { gap: space.s1_5 },
     2: { gap: space.s2 },
     3: { gap: space.s3 },
     4: { gap: space.s4 },
     5: { gap: space.s5 },
     6: { gap: space.s6 },
     8: { gap: space.s8 },
     10: { gap: space.s10 },
     12: { gap: space.s12 },
   })
   const toggleGroupStyles = {
     root: styles.root,
     vertical: styles.vertical,
     spacing: spacingStyles,
     segmented: [states.well, styles.segmented],
   }

   type ToggleGroupProps<Value extends string> = Omit<
     ToggleGroupPrimitive.Props<Value>,
     "className" | "style"
   > & {
     variant?: ToggleVariant
     size?: ToggleSize
     spacing?: keyof typeof spacingStyles
     xstyle?: StyleXStyles
     className?: string
     style?: CSSProperties
   }

   function ToggleGroup<Value extends string = string>({
     variant,
     size,
     spacing = 2,
     orientation = "horizontal",
     children,
     xstyle,
     className,
     style,
     ...props
   }: ToggleGroupProps<Value>) {
     const parent = use(ToggleGroupContext)
     const segments = spacing === 0 && (variant ?? "default") === "default"
     const segmented = segments && !parent?.onWell
     const onWell = segments || !!parent?.onWell
     return (
       <ToggleGroupContext value={{ variant, size, onWell, segmented }}>
         <ButtonGroupContext value={spacing === 0 && !segments ? { orientation } : null}>
           <ToggleGroupPrimitive
             data-slot="toggle-group"
             data-variant={variant ?? "default"}
             data-size={size ?? "default"}
             data-spacing={spacing}
             data-orientation={orientation}
             orientation={orientation}
             {...props}
             {...stateStyles(
               (state) => [
                 styles.root,
                 state.orientation === "vertical" && styles.vertical,
                 spacingStyles[spacing],
                 segmented && [states.well, styles.segmented],
                 xstyle,
               ],
               { className, style },
             )}
           >
             {children}
           </ToggleGroupPrimitive>
         </ButtonGroupContext>
       </ToggleGroupContext>
     )
   }

   function ToggleGroupItem<Value extends string = string>(props: ToggleProps<Value>) {
     return <Toggle data-slot="toggle-group-item" {...props} />
   }

   export { ToggleGroup, ToggleGroupItem, toggleGroupStyles }
   ```

4. Update the import paths to match your project setup.

## Usage

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

;<ToggleGroup aria-label="Options" defaultValue={["a"]}>
  <ToggleGroupItem value="a">A</ToggleGroupItem>
  <ToggleGroupItem value="b">B</ToggleGroupItem>
  <ToggleGroupItem value="c">C</ToggleGroupItem>
</ToggleGroup>
```

Pass `multiple` to allow more than one selection. `value` and `defaultValue`
are arrays in both modes; use `onValueChange` to control the selection.

RTL support is not included.

## Composition

Use `ToggleGroupItem` for each option.

```text
ToggleGroup
├── ToggleGroupItem
└── ToggleGroupItem
```

## Outline

Use `variant="outline"` for an outline style.

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

export default function ToggleGroupOutline() {
  return (
    <ToggleGroup aria-label="Calls" variant="outline" defaultValue={["all"]}>
      <ToggleGroupItem value="all" aria-label="Toggle all">
        All
      </ToggleGroupItem>
      <ToggleGroupItem value="missed" aria-label="Toggle missed">
        Missed
      </ToggleGroupItem>
    </ToggleGroup>
  )
}
```

## Size

Use `size` to set the size of every item.

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

export default function ToggleGroupSizes() {
  return (
    <VStack gap={4}>
      {(["sm", "default", "lg"] as const).map((size) => (
        <ToggleGroup
          key={size}
          aria-label={`${size} position`}
          size={size}
          defaultValue={["top"]}
          variant="outline"
        >
          <ToggleGroupItem value="top">Top</ToggleGroupItem>
          <ToggleGroupItem value="bottom">Bottom</ToggleGroupItem>
          <ToggleGroupItem value="left">Left</ToggleGroupItem>
          <ToggleGroupItem value="right">Right</ToggleGroupItem>
        </ToggleGroup>
      ))}
    </VStack>
  )
}
```

## Segmented

With `spacing={0}` and the default variant, the group is a segmented control. The
items sit in a shallow well and the pressed one is raised out of it. Inside a
[Toolbar](https://carbon.style/docs/components/toolbar) the toolbar is the well, so the group draws
none of its own.

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

export default function ToggleGroupSegmented() {
  return (
    <VStack gap={4}>
      {(["sm", "default", "lg"] as const).map((size) => (
        <ToggleGroup
          key={size}
          aria-label={`${size} view`}
          size={size}
          spacing={0}
          defaultValue={["list"]}
        >
          <ToggleGroupItem value="list">List</ToggleGroupItem>
          <ToggleGroupItem value="board">Board</ToggleGroupItem>
          <ToggleGroupItem value="calendar">Calendar</ToggleGroupItem>
        </ToggleGroup>
      ))}
    </VStack>
  )
}
```

## Spacing

With `variant="outline"`, use `spacing={0}` to join the items, or a spacing step to
separate them.

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

export default function ToggleGroupSpacing() {
  return (
    <VStack gap={4}>
      {([0, 2] as const).map((spacing) => (
        <ToggleGroup
          key={spacing}
          aria-label={spacing === 0 ? "Joined position" : "Spaced position"}
          size="sm"
          defaultValue={["top"]}
          variant="outline"
          spacing={spacing}
        >
          <ToggleGroupItem value="top">Top</ToggleGroupItem>
          <ToggleGroupItem value="bottom">Bottom</ToggleGroupItem>
          <ToggleGroupItem value="left">Left</ToggleGroupItem>
          <ToggleGroupItem value="right">Right</ToggleGroupItem>
        </ToggleGroup>
      ))}
    </VStack>
  )
}
```

## Vertical

Use `orientation="vertical"` to stack items and navigate with the up and down arrow keys.

```tsx
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react"

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

export default function ToggleGroupVertical() {
  return (
    <ToggleGroup
      multiple
      orientation="vertical"
      spacing={0}
      variant="outline"
      defaultValue={["bold", "italic"]}
    >
      <ToggleGroupItem value="bold" aria-label="Toggle bold">
        <BoldIcon />
      </ToggleGroupItem>
      <ToggleGroupItem value="italic" aria-label="Toggle italic">
        <ItalicIcon />
      </ToggleGroupItem>
      <ToggleGroupItem value="underline" aria-label="Toggle underline">
        <UnderlineIcon />
      </ToggleGroupItem>
    </ToggleGroup>
  )
}
```

## Disabled

Use `disabled` on the group to disable every item, or on an item to disable only that option.

```tsx
import { VStack } from "@/components/ui/stack"
import { Bold, Italic, Underline } from "lucide-react"

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

export default function ToggleGroupDisabled() {
  return (
    <VStack gap={4}>
      <ToggleGroup aria-label="Formatting" disabled>
        <ToggleGroupItem value="bold" aria-label="Toggle bold">
          <Bold />
        </ToggleGroupItem>
        <ToggleGroupItem value="italic" aria-label="Toggle italic">
          <Italic />
        </ToggleGroupItem>
        <ToggleGroupItem value="underline" aria-label="Toggle underline">
          <Underline />
        </ToggleGroupItem>
      </ToggleGroup>
      <ToggleGroup aria-label="Available formatting" multiple>
        <ToggleGroupItem value="bold" aria-label="Toggle bold">
          <Bold />
        </ToggleGroupItem>
        <ToggleGroupItem value="italic" aria-label="Toggle italic" disabled>
          <Italic />
        </ToggleGroupItem>
        <ToggleGroupItem value="underline" aria-label="Toggle underline">
          <Underline />
        </ToggleGroupItem>
      </ToggleGroup>
    </VStack>
  )
}
```

## Custom

Use `xstyle` to customize the items and `value` to keep one weight selected.

```tsx
"use client"

import { useId, useState } from "react"
import * as stylex from "@stylexjs/stylex"
import { Field, FieldDescription, FieldTitle } from "@/components/ui/field"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { color, leading, radius, space, text } from "@/theme/tokens.stylex"

const styles = stylex.create({
  item: { width: space.s16, height: space.s16, flexDirection: "column", borderRadius: radius.xl },
  sample: { fontSize: text.xl2, lineHeight: leading.xl2 },
  label: { fontSize: text.xs, lineHeight: leading.xs, color: color.mutedForeground },
})
const weights = stylex.create({
  light: { fontWeight: 300 },
  normal: { fontWeight: 400 },
  medium: { fontWeight: 500 },
  bold: { fontWeight: 700 },
})

export default function ToggleGroupFontWeightSelector() {
  const id = useId()
  const [fontWeight, setFontWeight] = useState<keyof typeof weights>("normal")
  return (
    <Field>
      <FieldTitle id={id}>Font Weight</FieldTitle>
      <ToggleGroup
        aria-labelledby={id}
        value={[fontWeight]}
        onValueChange={(value) => {
          if (value[0]) setFontWeight(value[0])
        }}
        variant="outline"
        spacing={2}
        size="lg"
      >
        {(["light", "normal", "medium", "bold"] as const).map((weight) => (
          <ToggleGroupItem key={weight} value={weight} aria-label={weight} xstyle={styles.item}>
            <span {...stylex.props(styles.sample, weights[weight])}>Aa</span>
            <span {...stylex.props(styles.label)}>
              {weight.charAt(0).toUpperCase() + weight.slice(1)}
            </span>
          </ToggleGroupItem>
        ))}
      </ToggleGroup>
      <FieldDescription>
        Use <code>font-{fontWeight}</code> to set the font weight.
      </FieldDescription>
    </Field>
  )
}
```

## API reference

`ToggleGroup` accepts [Base UI Toggle Group props](https://base-ui.com/react/components/toggle-group).
`ToggleGroupItem` accepts [Toggle props](https://carbon.style/docs/components/toggle). Both take `render`,
`xstyle`, `className` and real inline `style`.

### ToggleGroup

| Prop      | Type                                                             | Default     |
| --------- | ---------------------------------------------------------------- | ----------- |
| `variant` | `"default" \| "outline"`                                         | `"default"` |
| `size`    | `"sm" \| "default" \| "lg"`                                      | `"default"` |
| `spacing` | `0 \| 0.5 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 5 \| 6 \| 8 \| 10 \| 12` | `2`         |

Group-level `variant` and `size` override item values. When omitted, each item
uses its own value or its default.

### toggleGroupStyles

| Key         | What it is                       |
| ----------- | -------------------------------- |
| `root`      | Fitted row with stretching items |
| `vertical`  | Column layout                    |
| `spacing`   | Gaps on the token scale          |
| `segmented` | The well a segmented group draws |

### Data attributes

| Attribute                       | On             | Effect                                               |
| ------------------------------- | -------------- | ---------------------------------------------------- |
| `data-slot="toggle-group"`      | root           | Targets the group                                    |
| `data-slot="toggle-group-item"` | item           | Targets an option                                    |
| `data-variant`, `data-size`     | root and items | Mirror the variant and size                          |
| `data-spacing`                  | root           | Mirrors `spacing`                                    |
| `data-orientation`              | root           | Mirrors `orientation`                                |
| `data-disabled`                 | root and items | Base UI writes the disabled state                    |
| `data-multiple`                 | root           | Base UI writes it when multiple selection is enabled |
| `data-pressed`                  | items          | Base UI writes the selection state                   |
