# Toggle

A two-state button that can be either on or off.

```tsx
import { BookmarkIcon } from "lucide-react"

import { Toggle } from "@/components/ui/toggle"

export default function ToggleDemo() {
  return (
    <Toggle aria-label="Toggle bookmark" size="sm" variant="outline">
      <BookmarkIcon />
      Bookmark
    </Toggle>
  )
}
```

## 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` for icon sizing.
   Install [Button](https://carbon.style/docs/components/button) for `buttonStyles` and `ButtonGroupContext`.

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

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

   import { createContext, use, type CSSProperties } from "react"
   import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"

   import { ButtonGroupContext, buttonStyles } from "@/components/ui/button"
   import { stateStyles } from "@/lib/state-styles"
   import { states } from "@/theme/states"
   import { color, radius, space } from "@/theme/tokens.stylex"

   const pressedBg = "light-dark(oklch(0.93 0 0), oklch(0.3 0 0))"

   const styles = stylex.create({
     pressed: {
       backgroundColor: pressedBg,
       color: color.foreground,
       "--carbon-button-highlight": color.transparent,
       "--carbon-button-drop": color.transparent,
     },
     pressedKey: {
       color: color.secondaryForeground,
       backgroundClip: "border-box",
       "--carbon-button-highlight": color.buttonSecondaryHighlight,
       "--carbon-button-drop": color.buttonSecondaryDrop,
     },
     onWell: { backgroundColor: { default: null, ":hover": color.itemHighlightBg } },
     segment: {
       "--carbon-button-radius": `calc(var(--carbon-toggle-group-radius, ${radius.lg}) - 3px)`,
     },
   })
   const minimums = stylex.create({
     default: { minWidth: space.s8 },
     sm: { minWidth: space.s7 },
     lg: { minWidth: space.s9 },
   })
   const variants = { default: buttonStyles.variants.ghost, outline: buttonStyles.variants.outline }
   const sizes = {
     default: [buttonStyles.sizes.default, minimums.default],
     sm: [buttonStyles.sizes.sm, minimums.sm],
     lg: [buttonStyles.sizes.lg, minimums.lg],
   }
   const toggleStyles = {
     base: buttonStyles.base,
     variants,
     sizes,
     pressed: styles.pressed,
     pressedKey: [states.key, states.keySurface, styles.pressedKey],
     onWell: styles.onWell,
     pressedOnWell: states.wellKey,
     segment: styles.segment,
     disabled: states.disabled,
   }
   type ToggleVariant = keyof typeof variants
   type ToggleSize = keyof typeof sizes

   const ToggleGroupContext = createContext<{
     variant?: ToggleVariant
     size?: ToggleSize
     onWell?: boolean
     segmented?: boolean
   } | null>(null)

   type ToggleProps<Value extends string = string> = Omit<
     TogglePrimitive.Props<Value>,
     "className" | "style"
   > & {
     variant?: ToggleVariant
     size?: ToggleSize
     xstyle?: StyleXStyles
     className?: string
     style?: CSSProperties
   }

   function Toggle<Value extends string = string>({
     variant,
     size,
     xstyle,
     className,
     style,
     ...props
   }: ToggleProps<Value>) {
     const group = use(ToggleGroupContext)
     const joined = use(ButtonGroupContext)
     const resolvedVariant = group?.variant ?? variant ?? "default"
     const resolvedSize = group?.size ?? size ?? "default"
     return (
       <TogglePrimitive
         data-slot="toggle"
         data-variant={resolvedVariant}
         data-size={resolvedSize}
         {...props}
         {...stateStyles(
           (state) => [
             buttonStyles.base,
             variants[resolvedVariant],
             sizes[resolvedSize],
             group?.onWell && resolvedVariant === "default" && styles.onWell,
             group?.segmented && styles.segment,
             state.pressed && !group?.onWell && styles.pressed,
             state.pressed &&
               group?.onWell && [states.key, states.keySurface, styles.pressedKey, states.wellKey],
             props["aria-invalid"] && states.invalidRing,
             state.disabled && states.disabled,
             joined && buttonStyles.group[joined.orientation],
             xstyle,
           ],
           { className, style, hook: "carbon-icons" },
         )}
       />
     )
   }

   export {
     Toggle,
     ToggleGroupContext,
     toggleStyles,
     type ToggleProps,
     type ToggleVariant,
     type ToggleSize,
   }
   ```

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

## Usage

```tsx
import { Toggle } from "@/components/ui/toggle"

;<Toggle>Toggle</Toggle>
```

Use `defaultPressed` for an initial selection, or `pressed` and `onPressedChange`
to control it. Give an icon-only toggle an `aria-label`.

RTL support is not included.

## Outline

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

```tsx
import { HStack } from "@/components/ui/stack"
import { BoldIcon, ItalicIcon } from "lucide-react"

import { Toggle } from "@/components/ui/toggle"

export default function ToggleOutline() {
  return (
    <HStack wrap="wrap" align="center" gap={2}>
      <Toggle variant="outline" aria-label="Toggle italic">
        <ItalicIcon />
        Italic
      </Toggle>
      <Toggle variant="outline" aria-label="Toggle bold">
        <BoldIcon />
        Bold
      </Toggle>
    </HStack>
  )
}
```

## With text

Place text beside the icon to label the toggle.

```tsx
import { ItalicIcon } from "lucide-react"

import { Toggle } from "@/components/ui/toggle"

export default function ToggleText() {
  return (
    <Toggle aria-label="Toggle italic">
      <ItalicIcon />
      Italic
    </Toggle>
  )
}
```

## Size

Use `size` to change the height and minimum width.

```tsx
import { HStack } from "@/components/ui/stack"
import { Toggle } from "@/components/ui/toggle"

export default function ToggleSizes() {
  return (
    <HStack wrap="wrap" align="center" gap={2}>
      <Toggle variant="outline" aria-label="Toggle small" size="sm">
        Small
      </Toggle>
      <Toggle variant="outline" aria-label="Toggle default" size="default">
        Default
      </Toggle>
      <Toggle variant="outline" aria-label="Toggle large" size="lg">
        Large
      </Toggle>
    </HStack>
  )
}
```

## Disabled

Use `disabled` to prevent interaction.

```tsx
import { HStack } from "@/components/ui/stack"
import { Toggle } from "@/components/ui/toggle"

export default function ToggleDisabled() {
  return (
    <HStack wrap="wrap" align="center" gap={2}>
      <Toggle aria-label="Toggle disabled" disabled>
        Disabled
      </Toggle>
      <Toggle variant="outline" aria-label="Toggle disabled outline" disabled>
        Disabled
      </Toggle>
    </HStack>
  )
}
```

## API reference

`Toggle` accepts [Base UI Toggle props](https://base-ui.com/react/components/toggle),
including `render`, plus `xstyle`, `className` and real inline `style`.

### Toggle

| Prop      | Type                        | Default     |
| --------- | --------------------------- | ----------- |
| `variant` | `"default" \| "outline"`    | `"default"` |
| `size`    | `"sm" \| "default" \| "lg"` | `"default"` |

A surrounding `ToggleGroup` supplies `variant` and `size` when set on the group.

### toggleStyles

| Key             | What it is                                          |
| --------------- | --------------------------------------------------- |
| `base`          | Button base paired with the shared focus ring       |
| `variants`      | Default ghost and outline button styles             |
| `sizes`         | Button heights and minimum widths                   |
| `pressed`       | The flat fill and its text colour                   |
| `pressedKey`    | The raised key a pressed toggle is in a well        |
| `onWell`        | Row-highlight hover for a ghost toggle on a well    |
| `pressedOnWell` | The flat pill face a pressed toggle takes in a well |
| `segment`       | Corner nested in a segmented group's well           |
| `disabled`      | Shared disabled opacity and pointer handling        |

`ToggleGroupContext` carries group-level `variant` and `size` to the toggle, and
tells it when it sits in a well: a [Toolbar](https://carbon.style/docs/components/toolbar) or a
segmented [Toggle Group](https://carbon.style/docs/components/toggle-group).

### Data attributes

| Attribute                       | On     | Effect                               |
| ------------------------------- | ------ | ------------------------------------ |
| `data-slot="toggle"`            | button | Targets the toggle                   |
| `data-variant`, `data-size`     | button | Mirror the resolved variant and size |
| `data-pressed`, `data-disabled` | button | Base UI writes the current state     |
