---
title: Toggle Group
description: "Astro toggle buttons that share one selection or several selections."
sidebar:
    badge: new
seo:
    title: "Toggle Group for Astro - examples and usage"
related:
    - Toggle Group: /components/toggle-group
    - Input Group: /components/input-group
---

```astro
---
import AlignCenterIcon from "@lucide/astro/icons/text-align-center"
import AlignLeftIcon from "@lucide/astro/icons/text-align-start"
import AlignRightIcon from "@lucide/astro/icons/text-align-end"

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

<ToggleGroup
  variant="outline"
  defaultValue="center"
  aria-label="Text alignment"
>
  <ToggleGroupItem value="left" aria-label="Align left">
    <AlignLeftIcon />
  </ToggleGroupItem>
  <ToggleGroupItem value="center" aria-label="Align center">
    <AlignCenterIcon />
  </ToggleGroupItem>
  <ToggleGroupItem value="right" aria-label="Align right">
    <AlignRightIcon />
  </ToggleGroupItem>
</ToggleGroup>
```

## Installation

### Command

```sh
bunx --bun shadcn@latest add @gnostic/toggle-group
```

### Manual

Complete the [Installation guide](/docs/installation) first. Then copy the component files below.

Install the component packages:

```sh
bun add @data-slot/toggle-group
```

#### src/components/ui/toggle-group/index.ts

```ts
export { default as ToggleGroup } from './toggle-group.astro'
export { default as ToggleGroupItem } from './toggle-group-item.astro'
export { default as ToggleGroupSeparator } from './toggle-group-separator.astro'
export {
    toggleGroupItemVariants,
    toggleGroupVariants,
    type ToggleGroupItemVariantProps,
    type ToggleGroupVariantProps,
} from './toggle-group-variants.ts'

export type {
    ToggleGroupItemProps,
    ToggleGroupProps,
    ToggleGroupSeparatorProps,
} from './types.ts'
```

#### src/components/ui/toggle-group/toggle-group-item.astro

```astro
---
import type { HTMLAttributes } from "astro/types"

import { cn } from "cn"
import { toggleGroupItemVariants } from "./toggle-group-variants.ts"
import type { ToggleGroupItemProps } from "./types.ts"

/** Button attributes pass through. `value` is required by `ToggleGroupItemProps`. */
type Props = Omit<HTMLAttributes<"button">, "value"> & ToggleGroupItemProps

const {
  class: className,
  value,
  variant,
  size,
  disabled,
  type = "button",
  ...props
} = Astro.props
---

<!--
  This button is separate from Toggle.
  The group controller selects data-slot="toggle-group-item".
  Null leaves variant or size to the group variants.
-->
<button
  data-slot="toggle-group-item"
  data-value={value}
  data-variant={variant}
  data-size={size}
  data-disabled={disabled}
  type={type}
  disabled={disabled}
  aria-pressed="false"
  class={cn(
    toggleGroupItemVariants({
      size: size ?? null,
      variant: variant ?? null,
    }),
    className,
  )}
  {...props}
>
  <slot />
</button>
```

#### src/components/ui/toggle-group/toggle-group-separator.astro

```astro
---
import type { HTMLAttributes } from "astro/types"

import { cn } from "cn"
import { Separator } from "../separator/index.ts"
import type { ToggleGroupSeparatorProps } from "./types.ts"

/** Separator attributes pass through. */
type Props = HTMLAttributes<"div"> & ToggleGroupSeparatorProps

const { class: className, orientation = "vertical", ...props } = Astro.props
---

<Separator
  orientation={orientation}
  class={cn(
    "pointer-events-none relative z-2 bg-input before:absolute before:inset-0 dark:before:bg-input/32",
    className,
  )}
  {...props}
/>
```

#### src/components/ui/toggle-group/toggle-group-variants.ts

```ts
import { cva, type VariantProps } from 'class-variance-authority'

import {
    toggleBaseClass,
    toggleSizeClass,
    toggleVariantClass,
} from '../toggle/toggle-variants.ts'

/**
 * Layout for the group.
 * `outline` joins direct children and paints the Toggle outline treatment
 * on each item that does not set `data-variant`.
 * `size` paints the Toggle size on each item that does not set `data-size`.
 * The default variant keeps a small gap.
 * Separator rules follow the pressed and hover state of a neighboring item.
 * `disabled` blocks pointer events on each item.
 */
export const toggleGroupVariants = cva(
    'group/toggle-group dark:*:[[data-slot=separator]:has(+[data-slot=toggle-group-item]:hover)]:before:bg-input/64 dark:*:[[data-slot=separator]:has(+[data-slot=toggle-group-item][aria-pressed=true])]:before:bg-input dark:*:[[data-slot=toggle-group-item]:hover+[data-slot=separator]]:before:bg-input/64 dark:*:[[data-slot=toggle-group-item][aria-pressed=true]+[data-slot=separator]]:before:bg-input flex w-fit *:focus-visible:z-10',
    {
        compoundVariants: [
            {
                class: '*:not-first:rounded-s-none *:not-first:border-s-0 *:not-last:rounded-e-none *:not-last:border-e-0 *:not-first:before:rounded-s-none *:not-last:before:rounded-e-none *:not-first:not-data-[slot=separator]:before:inset-s-[-0.5px] *:not-last:not-data-[slot=separator]:before:inset-e-[-0.5px]',
                orientation: 'horizontal',
                variant: 'outline',
            },
            {
                class: '*:not-first:rounded-t-none *:not-first:border-t-0 *:not-last:rounded-b-none *:not-last:border-b-0 *:not-first:before:rounded-t-none *:not-last:before:rounded-b-none *:not-first:not-data-[slot=separator]:before:top-[-0.5px] *:not-last:not-data-[slot=separator]:before:bottom-[-0.5px] *:data-[slot=toggle-group-item]:not-last:before:hidden dark:*:first:before:block dark:*:last:before:hidden',
                orientation: 'vertical',
                variant: 'outline',
            },
        ],
        defaultVariants: {
            disabled: false,
            orientation: 'horizontal',
            size: 'default',
            variant: 'default',
        },
        variants: {
            disabled: {
                false: '',
                true: 'cursor-not-allowed *:data-[slot=toggle-group-item]:pointer-events-none *:data-[slot=toggle-group-item]:opacity-64',
            },
            orientation: {
                horizontal: '*:pointer-coarse:after:min-w-auto',
                vertical: 'flex-col *:pointer-coarse:after:min-h-auto',
            },
            size: {
                default:
                    '*:data-[slot=toggle-group-item]:not-data-size:h-9 *:data-[slot=toggle-group-item]:not-data-size:min-w-9 *:data-[slot=toggle-group-item]:not-data-size:px-[calc(--spacing(2)-1px)] *:data-[slot=toggle-group-item]:not-data-size:sm:h-8 *:data-[slot=toggle-group-item]:not-data-size:sm:min-w-8',
                lg: '*:data-[slot=toggle-group-item]:not-data-size:h-10 *:data-[slot=toggle-group-item]:not-data-size:min-w-10 *:data-[slot=toggle-group-item]:not-data-size:px-[calc(--spacing(2.5)-1px)] *:data-[slot=toggle-group-item]:not-data-size:sm:h-9 *:data-[slot=toggle-group-item]:not-data-size:sm:min-w-9',
                sm: '*:data-[slot=toggle-group-item]:not-data-size:h-8 *:data-[slot=toggle-group-item]:not-data-size:min-w-8 *:data-[slot=toggle-group-item]:not-data-size:px-[calc(--spacing(1.5)-1px)] *:data-[slot=toggle-group-item]:not-data-size:sm:h-7 *:data-[slot=toggle-group-item]:not-data-size:sm:min-w-7',
            },
            variant: {
                default:
                    'gap-0.5 *:data-[slot=toggle-group-item]:not-data-variant:border-transparent',
                outline:
                    '*:data-[slot=toggle-group-item]:not-data-variant:border-input *:data-[slot=toggle-group-item]:not-data-variant:bg-background *:data-[slot=toggle-group-item]:not-data-variant:dark:bg-input/32 *:data-[slot=toggle-group-item]:not-data-variant:dark:aria-pressed:bg-input *:data-[slot=toggle-group-item]:not-data-variant:dark:hover:bg-input/64 *:data-[slot=toggle-group-item]:not-data-variant:shadow-xs/5 *:data-[slot=toggle-group-item]:not-data-variant:not-dark:bg-clip-padding *:data-[slot=toggle-group-item]:not-data-variant:not-disabled:not-active:not-aria-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] *:data-[slot=toggle-group-item]:not-data-variant:dark:not-disabled:not-aria-pressed:before:shadow-[0_-1px_--theme(--color-white/2%)] *:data-[slot=toggle-group-item]:not-data-variant:dark:not-disabled:not-active:not-aria-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] *:data-[slot=toggle-group-item]:not-data-variant:[:disabled,:active,[aria-pressed=true]]:shadow-none',
            },
        },
    },
)

export type ToggleGroupVariantProps = VariantProps<typeof toggleGroupVariants>

/**
 * Styles for one item.
 * `variant` and `size` use the Toggle classes.
 * Pass `null` for an omitted value. The group variant then owns that axis.
 */
export const toggleGroupItemVariants = cva(
    `${toggleBaseClass} aria-pressed:z-10`,
    {
        variants: {
            size: toggleSizeClass,
            variant: toggleVariantClass,
        },
    },
)

export type ToggleGroupItemVariantProps = VariantProps<
    typeof toggleGroupItemVariants
>
```

#### src/components/ui/toggle-group/toggle-group.astro

```astro
---
import type { HTMLAttributes } from "astro/types"

import { cn } from "cn"
import type { ToggleGroupProps } from "./types.ts"
import { toggleGroupVariants } from "./toggle-group-variants.ts"

/** Div attributes pass through. Give the group an accessible name. */
type Props = HTMLAttributes<"div"> & ToggleGroupProps

const {
  class: className,
  variant = "default",
  size = "default",
  orientation = "horizontal",
  defaultValue,
  multiple,
  loop,
  disabled,
  ...props
} = Astro.props

/**
 * The controller reads a space-separated list.
 * An array and a string with spaces become that list.
 */
const defaultValueAttribute = Array.isArray(defaultValue)
  ? defaultValue
      .flatMap((value) => value.split(/\s+/))
      .filter((value) => value.length > 0)
      .join(" ")
  : defaultValue
---

<div
  data-slot="toggle-group"
  data-variant={variant}
  data-size={size}
  data-orientation={orientation}
  data-default-value={defaultValueAttribute}
  data-multiple={multiple ? "" : undefined}
  data-loop={loop === false ? "false" : undefined}
  data-disabled={disabled ? "" : undefined}
  role="group"
  aria-disabled={disabled ? "true" : undefined}
  aria-orientation={orientation === "vertical" ? "vertical" : undefined}
  class={cn(
    toggleGroupVariants({ disabled, orientation, size, variant }),
    className,
  )}
  {...props}
>
  <slot />
</div>

<script is:inline>
  // Paint the pressed items before the module controller binds.
  // Skip a group that already has a live selection.
  {
    const script = document.currentScript
    const root = script && script.previousElementSibling
    if (
      root instanceof HTMLElement &&
      root.getAttribute("data-slot") === "toggle-group" &&
      !root.hasAttribute("data-value")
    ) {
      const multiple = root.hasAttribute("data-multiple")
      const items = []
      for (const item of root.querySelectorAll(
        '[data-slot="toggle-group-item"]',
      )) {
        const owner =
          item.parentElement &&
          item.parentElement.closest('[data-slot="toggle-group"]')
        if (owner === root) items.push(item)
      }
      const known = new Set()
      for (const item of items) {
        const value = (item.getAttribute("data-value") || "").trim()
        if (value) known.add(value)
      }
      const selected = new Set()
      for (const value of (root.getAttribute("data-default-value") || "").split(
        /\s+/,
      )) {
        if (!value || !known.has(value)) continue
        selected.add(value)
        if (!multiple) break
      }
      for (const item of items) {
        const value = (item.getAttribute("data-value") || "").trim()
        const pressed = value !== "" && selected.has(value)
        item.setAttribute("aria-pressed", pressed ? "true" : "false")
        item.setAttribute("data-state", pressed ? "on" : "off")
      }
    }
  }
</script>

<script>
  import { create, type ToggleGroupController } from "@data-slot/toggle-group"

  // Destroy bindings before a view transition so the next page-load binds once.
  const controllers: ToggleGroupController[] = []
  const initialize = () => controllers.push(...create())
  initialize()
  document.addEventListener("astro:page-load", initialize)
  document.addEventListener("astro:before-swap", () => {
    for (const controller of controllers) controller.destroy()
    controllers.length = 0
  })
</script>
```

#### src/components/ui/toggle-group/types.ts

```ts
import type { ToggleVariantProps } from '../toggle/toggle-variants.ts'

/**
 * Props for a set of toggle buttons that share a selection.
 * Div attributes such as `class`, `id`, and `aria-label` pass through.
 * Give the group an accessible name with `aria-label` or `aria-labelledby`.
 * Place each item directly inside the group. A separator may sit between items.
 */
export interface ToggleGroupProps {
    /**
     * Visual style for every item that does not set its own `variant`.
     * `outline` joins the items into one control. The default style leaves a small gap.
     *
     * @default "default"
     */
    variant?: ToggleVariantProps['variant']
    /**
     * Height and padding for every item that does not set its own `size`.
     *
     * @default "default"
     */
    size?: ToggleVariantProps['size']
    /**
     * Direction of the items and of the arrow keys.
     *
     * @default "horizontal"
     */
    orientation?: 'horizontal' | 'vertical'
    /**
     * Value selected at first render.
     * Pass one value, a space-separated list, or an array.
     * A value matches an item `value`. Use a value with no spaces.
     * In single selection, the first known value is selected.
     */
    defaultValue?: string | string[]
    /**
     * Allow more than one pressed item.
     *
     * @default false
     */
    multiple?: boolean
    /**
     * Move focus from the last item to the first item, and the reverse.
     *
     * @default true
     */
    loop?: boolean
    /**
     * Block changes to every item.
     *
     * @default false
     */
    disabled?: boolean
}

/**
 * Props for one toggle in the group.
 * Button attributes such as `class` and `aria-label` pass through.
 * The control is its own button. It uses the Toggle styles.
 */
export interface ToggleGroupItemProps {
    /**
     * Value stored for this item.
     * The group matches this value against `defaultValue`.
     * Use a value with no spaces.
     */
    value: string
    /**
     * Visual style for this item.
     * When omitted, the item uses the group's `variant`.
     */
    variant?: ToggleVariantProps['variant']
    /**
     * Control size for this item.
     * When omitted, the item uses the group's `size`.
     */
    size?: ToggleVariantProps['size']
    /**
     * Block changes to this item.
     *
     * @default false
     */
    disabled?: boolean
}

/**
 * Props for a divider between toggle group items.
 * Separator attributes such as `class` pass through.
 */
export interface ToggleGroupSeparatorProps {
    /**
     * Direction of the divider line.
     * Use `vertical` between items in a horizontal group.
     * Use `horizontal` between items in a vertical group.
     *
     * @default "vertical"
     */
    orientation?: 'horizontal' | 'vertical'
}
```

Optional preset: inspect a stylesheet at /presets/{style}.css, save it to src/styles/presets/{style}.css, and import it after the global stylesheet. Set data-preset="{style}" on your layout or a component container. See [Theming](/docs/theming) for details.


## Usage

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

```astro index.astro wrap lineNumbers
<ToggleGroup
    variant="outline"
    defaultValue="center"
    aria-label="Text alignment"
  >
  <ToggleGroupItem
    value="left"
    aria-label="Align left"
  >
  <AlignLeftIcon />Left</ToggleGroupItem>
  <ToggleGroupItem
    value="center"
    aria-label="Align center"
  >
  <AlignCenterIcon />Center</ToggleGroupItem>
  <ToggleGroupItem
    value="right"
    aria-label="Align right"
    >
  <AlignRightIcon />Right</ToggleGroupItem>
</ToggleGroup>
```

Set `multiple` when more than one item can stay pressed. Pass several values as an array or as a space-separated string.

```astro index.astro wrap lineNumbers
<ToggleGroup
  multiple
  defaultValue={["bold", "italic"]}
  aria-label="Text formatting"
  >
  <ToggleGroupItem value="bold">Bold</ToggleGroupItem>
  <ToggleGroupItem value="italic">Italic</ToggleGroupItem>
  <ToggleGroupItem value="underline">Underline</ToggleGroupItem>
</ToggleGroup>
```

`variant` and `size` on the group apply to each item that does not set its own value. `outline` joins the items. The default variant leaves a small gap. Each item uses the [Toggle](/components/toggle) styles.

## Composition

Place the parts directly inside `ToggleGroup`:

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

`ToggleGroupSeparator` is optional. Use `orientation="horizontal"` on the separator when the group is vertical.

## Examples

### Several selections

```astro
---
import BoldIcon from "@lucide/astro/icons/bold"
import ItalicIcon from "@lucide/astro/icons/italic"
import UnderlineIcon from "@lucide/astro/icons/underline"

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

<ToggleGroup
  multiple
  defaultValue={["bold", "italic"]}
  aria-label="Text formatting"
>
  <ToggleGroupItem value="bold" aria-label="Bold">
    <BoldIcon />
  </ToggleGroupItem>
  <ToggleGroupItem value="italic" aria-label="Italic">
    <ItalicIcon />
  </ToggleGroupItem>
  <ToggleGroupItem value="underline" aria-label="Underline">
    <UnderlineIcon />
  </ToggleGroupItem>
</ToggleGroup>
```

### Disabled

`disabled` on the group blocks every item. `disabled` on one item blocks that item.

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

<div class="flex flex-wrap items-center gap-6">
  <ToggleGroup
    disabled
    variant="outline"
    defaultValue="center"
    aria-label="Locked alignment"
  >
    <ToggleGroupItem value="left">Left</ToggleGroupItem>
    <ToggleGroupItem value="center">Center</ToggleGroupItem>
    <ToggleGroupItem value="right">Right</ToggleGroupItem>
  </ToggleGroup>
  <ToggleGroup variant="outline" defaultValue="left" aria-label="Alignment">
    <ToggleGroupItem value="left">Left</ToggleGroupItem>
    <ToggleGroupItem value="center" disabled>
      Center
    </ToggleGroupItem>
    <ToggleGroupItem value="right">Right</ToggleGroupItem>
  </ToggleGroup>
</div>
```

### Sizes

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

<div class="flex flex-col items-start gap-4">
  {(["sm", "default", "lg"] as const).map((size) => (
    <ToggleGroup
      variant="outline"
      size={size}
      defaultValue="center"
      aria-label={size + " alignment"}
    >
      <ToggleGroupItem value="left">Left</ToggleGroupItem>
      <ToggleGroupItem value="center">Center</ToggleGroupItem>
      <ToggleGroupItem value="right">Right</ToggleGroupItem>
    </ToggleGroup>
  ))}
</div>
```

### Vertical

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

<ToggleGroup
  orientation="vertical"
  variant="outline"
  defaultValue="start"
  aria-label="Block alignment"
>
  <ToggleGroupItem value="start">Start</ToggleGroupItem>
  <ToggleGroupSeparator orientation="horizontal" />
  <ToggleGroupItem value="center">Center</ToggleGroupItem>
  <ToggleGroupSeparator orientation="horizontal" />
  <ToggleGroupItem value="end">End</ToggleGroupItem>
</ToggleGroup>
```

## API Reference

### ToggleGroup

<AutoTypeTable
	path="../../packages/ui/src/components/ui/toggle-group/types.ts"
	name="ToggleGroupProps"
/>

Div attributes such as `class`, `id`, and `aria-label` pass through.

### ToggleGroupItem

<AutoTypeTable
	path="../../packages/ui/src/components/ui/toggle-group/types.ts"
	name="ToggleGroupItemProps"
/>

Button attributes such as `class` and `aria-label` pass through. Give an icon-only item an accessible name.

### ToggleGroupSeparator

<AutoTypeTable
	path="../../packages/ui/src/components/ui/toggle-group/types.ts"
	name="ToggleGroupSeparatorProps"
/>

Separator attributes such as `class` pass through.

Read the [installed source](/r/toggle-group.json) for the component source.
See the [data-slot docs](https://data-slot.com/components/toggle-group/) for the controller, events, and keyboard model.

## When to use Toggle Group

Use Toggle Group when the visitor keeps one option pressed, or several options pressed, and every option stays visible. Put the group label in `aria-label` when nearby text does not name the group. Use one item value with no spaces.

A change event on the group carries the selected values. Listen for `toggle-group:change` on the group element. `event.detail.value` is an array of strings.

## Keyboard and behavior

Tab moves to the active item. Arrow keys move focus along `orientation`. Home moves to the first item. End moves to the last item. Enter and Space press the focused item. Set `loop={false}` to stop focus at the ends.

One item stays pressed at a time unless `multiple` is set. Press the active item again to clear it. A disabled group and a disabled item ignore pointer and keyboard changes.

## Theme and layout

Pressed, hover, focus, and disabled styles use the Toggle variants and the current theme. Check an icon-only item with an accessible name. In an outline group, keep item sizes equal so the shared border stays even. Color should reinforce the pressed state, not be its only indication.
