---
type: doc
title: Button Group
description: Groups related controls with connected edges and consistent focus behavior.
---

> Fulldev UI for Astro. The `@fulldev` registry in `components.json` points at `https://ui.full.dev/r/styles/{style}/{name}.json`; see [installation](/astro/docs/installation.md).

The official demo uses a dropdown submenu, which the Astro dropdown menu does not have, so it is in the [React Button Group](/react/components/button-group/) docs only.

## Installation

```bash
npx shadcn@latest add @fulldev/button-group
```

## Usage

```ts
import {
  ButtonGroup,
  ButtonGroupSeparator,
  ButtonGroupText,
} from "@/components/ui/button-group"
```

```astro
<ButtonGroup aria-label="Document actions">
  <Button variant="outline">Save</Button>
  <Button variant="outline">Publish</Button>
</ButtonGroup>
```

`ButtonGroup` renders a `div` with `role="group"`. Give each group an
accessible name with `aria-label` or `aria-labelledby`. The group connects the
edges of its direct children that have a `data-slot`. Dropdown Menu, Popover
and Select render a wrapper element around their trigger, so give that trigger
the connected edge classes itself, such as `rounded-l-none border-l-0`.

Use `ButtonGroup` for buttons that perform an action and
[Toggle Group](/astro/components/toggle-group/) for buttons that toggle a state.

## Orientation

Set the `orientation` prop to change the button group layout.

```astro title="src/components/examples/button-group/button-group-orientation.astro"
---
import MinusIcon from "@lucide/astro/icons/minus"
import PlusIcon from "@lucide/astro/icons/plus"

import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
---

<ButtonGroup orientation="vertical" aria-label="Media controls" class="h-fit">
  <Button variant="outline" size="icon">
    <PlusIcon />
  </Button>
  <Button variant="outline" size="icon">
    <MinusIcon />
  </Button>
</ButtonGroup>
```

## Size

Control the size of buttons using the `size` prop on individual buttons.

```astro title="src/components/examples/button-group/button-group-size.astro"
---
import PlusIcon from "@lucide/astro/icons/plus"

import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
---

<div class="flex flex-col items-start gap-8">
  <ButtonGroup>
    <Button variant="outline" size="sm">
      Small
    </Button>
    <Button variant="outline" size="sm">
      Button
    </Button>
    <Button variant="outline" size="sm">
      Group
    </Button>
    <Button variant="outline" size="icon-sm">
      <PlusIcon />
    </Button>
  </ButtonGroup>
  <ButtonGroup>
    <Button variant="outline">Default</Button>
    <Button variant="outline">Button</Button>
    <Button variant="outline">Group</Button>
    <Button variant="outline" size="icon">
      <PlusIcon />
    </Button>
  </ButtonGroup>
  <ButtonGroup>
    <Button variant="outline" size="lg">
      Large
    </Button>
    <Button variant="outline" size="lg">
      Button
    </Button>
    <Button variant="outline" size="lg">
      Group
    </Button>
    <Button variant="outline" size="icon-lg">
      <PlusIcon />
    </Button>
  </ButtonGroup>
</div>
```

## Nested

Nest `ButtonGroup` components to create button groups with spacing.

```astro title="src/components/examples/button-group/button-group-nested.astro"
---
import AudioLinesIcon from "@lucide/astro/icons/audio-lines"
import PlusIcon from "@lucide/astro/icons/plus"

import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/components/ui/input-group"
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip"
---

<ButtonGroup>
  <ButtonGroup>
    <Button variant="outline" size="icon">
      <PlusIcon />
    </Button>
  </ButtonGroup>
  <ButtonGroup>
    <InputGroup>
      <InputGroupInput placeholder="Send a message..." />
      <InputGroupAddon align="inline-end">
        <Tooltip>
          <TooltipTrigger as="span" class="flex">
            <AudioLinesIcon />
          </TooltipTrigger>
          <TooltipContent>Voice Mode</TooltipContent>
        </Tooltip>
      </InputGroupAddon>
    </InputGroup>
  </ButtonGroup>
</ButtonGroup>
```

## Separator

`ButtonGroupSeparator` visually divides buttons within a group. Outline
buttons have a border and do not need one.

```astro title="src/components/examples/button-group/button-group-separator.astro"
---
import { Button } from "@/components/ui/button"
import { ButtonGroup, ButtonGroupSeparator } from "@/components/ui/button-group"
---

<ButtonGroup>
  <Button variant="secondary" size="sm">
    Copy
  </Button>
  <ButtonGroupSeparator />
  <Button variant="secondary" size="sm">
    Paste
  </Button>
</ButtonGroup>
```

## Split

Create a split button group by adding two buttons separated by a
`ButtonGroupSeparator`.

```astro title="src/components/examples/button-group/button-group-split.astro"
---
import { Button } from "@/components/ui/button"
import { ButtonGroup, ButtonGroupSeparator } from "@/components/ui/button-group"
---

<ButtonGroup>
  <Button variant="secondary">Button</Button>
  <ButtonGroupSeparator />
  <Button size="icon" variant="secondary">
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="tabler-icon tabler-icon-plus"
    >
      <path d="M12 5l0 14"></path>
      <path d="M5 12l14 0"></path>
    </svg>
  </Button>
</ButtonGroup>
```

## Input

Wrap an `Input` component with buttons.

```astro title="src/components/examples/button-group/button-group-input.astro"
---
import SearchIcon from "@lucide/astro/icons/search"

import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import { Input } from "@/components/ui/input"
---

<ButtonGroup>
  <Input placeholder="Search..." />
  <Button variant="outline" aria-label="Search">
    <SearchIcon />
  </Button>
</ButtonGroup>
```

## Input Group

Wrap an `InputGroup` component to create complex input layouts.

```astro title="src/components/examples/button-group/button-group-input-group.astro"
---
import AudioLinesIcon from "@lucide/astro/icons/audio-lines"
import PlusIcon from "@lucide/astro/icons/plus"

import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
} from "@/components/ui/input-group"
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip"
---

<ButtonGroup class="[--radius:9999rem]" data-voice-input>
  <ButtonGroup>
    <Button variant="outline" size="icon">
      <PlusIcon />
    </Button>
  </ButtonGroup>
  <ButtonGroup>
    <InputGroup>
      <InputGroupInput placeholder="Send a message..." />
      <InputGroupAddon align="inline-end">
        <Tooltip>
          <TooltipTrigger as="span" class="flex">
            <InputGroupButton
              size="icon-xs"
              data-active="false"
              class="data-[active=true]:bg-orange-100 data-[active=true]:text-orange-700 dark:data-[active=true]:bg-orange-800 dark:data-[active=true]:text-orange-100"
              aria-pressed="false"
            >
              <AudioLinesIcon />
            </InputGroupButton>
          </TooltipTrigger>
          <TooltipContent>Voice Mode</TooltipContent>
        </Tooltip>
      </InputGroupAddon>
    </InputGroup>
  </ButtonGroup>
</ButtonGroup>

<script>
  const initialize = () => {
    for (const root of document.querySelectorAll("[data-voice-input]")) {
      if (root.hasAttribute("data-bound")) continue
      root.setAttribute("data-bound", "")
      const input = root.querySelector("input")
      const button = root.querySelector("[aria-pressed]")
      button?.addEventListener("click", () => {
        const enabled = button.getAttribute("aria-pressed") !== "true"
        button.setAttribute("aria-pressed", String(enabled))
        button.setAttribute("data-active", String(enabled))
        if (!input) return
        input.disabled = enabled
        input.placeholder = enabled
          ? "Record and send audio..."
          : "Send a message..."
      })
    }
  }

  initialize()
  document.addEventListener("astro:page-load", initialize)
</script>
```

## Dropdown Menu

Create a split button group with a `DropdownMenu` component.

```astro title="src/components/examples/button-group/button-group-dropdown.astro"
---
import CheckIcon from "@lucide/astro/icons/check"
import ChevronDownIcon from "@lucide/astro/icons/chevron-down"
import CopyIcon from "@lucide/astro/icons/copy"
import ShareIcon from "@lucide/astro/icons/share"
import TrashIcon from "@lucide/astro/icons/trash"
import AlertTriangleIcon from "@lucide/astro/icons/triangle-alert"
import UserRoundXIcon from "@lucide/astro/icons/user-round-x"
import VolumeOffIcon from "@lucide/astro/icons/volume-off"

import { Button, buttonVariants } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
---

<ButtonGroup>
  <Button variant="outline">Follow</Button>
  <DropdownMenu>
    <DropdownMenuTrigger
      class={buttonVariants({
        variant: "outline",
        class: "rounded-l-none border-l-0 pl-2!",
      })}
    >
      <ChevronDownIcon />
    </DropdownMenuTrigger>
    <DropdownMenuContent align="end" class="w-44">
      <DropdownMenuGroup>
        <DropdownMenuItem>
          <VolumeOffIcon />
          Mute Conversation
        </DropdownMenuItem>
        <DropdownMenuItem>
          <CheckIcon />
          Mark as Read
        </DropdownMenuItem>
        <DropdownMenuItem>
          <AlertTriangleIcon />
          Report Conversation
        </DropdownMenuItem>
        <DropdownMenuItem>
          <UserRoundXIcon />
          Block User
        </DropdownMenuItem>
        <DropdownMenuItem>
          <ShareIcon />
          Share Conversation
        </DropdownMenuItem>
        <DropdownMenuItem>
          <CopyIcon />
          Copy Conversation
        </DropdownMenuItem>
      </DropdownMenuGroup>
      <DropdownMenuSeparator />
      <DropdownMenuGroup>
        <DropdownMenuItem variant="destructive">
          <TrashIcon />
          Delete Conversation
        </DropdownMenuItem>
      </DropdownMenuGroup>
    </DropdownMenuContent>
  </DropdownMenu>
</ButtonGroup>
```

## Select

Pair with a `Select` component.

```astro title="src/components/examples/button-group/button-group-select.astro"
---
import ArrowRightIcon from "@lucide/astro/icons/arrow-right"

import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import { Input } from "@/components/ui/input"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const CURRENCIES = [
  { label: "US Dollar", value: "$" },
  { label: "Euro", value: "€" },
  { label: "British Pound", value: "£" },
]
---

<ButtonGroup>
  <ButtonGroup>
    <Select defaultValue="$">
      <SelectTrigger class="rounded-r-none font-mono">
        <SelectValue />
      </SelectTrigger>
      <SelectContent position="popper" align="start">
        <SelectGroup>
          {CURRENCIES.map((item) => (
            <SelectItem value={item.value} label={item.value}>
              {item.value}{" "}
              <span class="text-muted-foreground">{item.label}</span>
            </SelectItem>
          ))}
        </SelectGroup>
      </SelectContent>
    </Select>
    <Input placeholder="10.00" pattern="[0-9]*" />
  </ButtonGroup>
  <ButtonGroup>
    <Button aria-label="Send" size="icon" variant="outline">
      <ArrowRightIcon />
    </Button>
  </ButtonGroup>
</ButtonGroup>
```

## Popover

Use with a `Popover` component.

```astro title="src/components/examples/button-group/button-group-popover.astro"
---
import BotIcon from "@lucide/astro/icons/bot"
import ChevronDownIcon from "@lucide/astro/icons/chevron-down"

import { Button, buttonVariants } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from "@/components/ui/popover"
import { Textarea } from "@/components/ui/textarea"
---

<ButtonGroup>
  <Button variant="outline">
    <BotIcon /> Copilot
  </Button>
  <Popover>
    <PopoverTrigger
      class={buttonVariants({
        variant: "outline",
        size: "icon",
        class: "rounded-l-none border-l-0",
      })}
      aria-label="Open Popover"
    >
      <ChevronDownIcon />
    </PopoverTrigger>
    <PopoverContent align="end" class="rounded-xl text-sm">
      <PopoverHeader>
        <PopoverTitle>Start a new task with Copilot</PopoverTitle>
        <PopoverDescription>
          Describe your task in natural language.
        </PopoverDescription>
      </PopoverHeader>
      <Field>
        <FieldLabel for="task" class="sr-only">
          Task Description
        </FieldLabel>
        <Textarea id="task" placeholder="I need to..." class="resize-none" />
        <FieldDescription>
          Copilot will open a pull request for review.
        </FieldDescription>
      </Field>
    </PopoverContent>
  </Popover>
</ButtonGroup>
```

## RTL

The official RTL example has the same dropdown submenu, so it is in the [React Button Group](/react/components/button-group/) docs only.

## API Reference

Source: [`src/components/ui/button-group`](https://github.com/fulldotdev/ui/tree/main/src/components/ui/button-group)

### ButtonGroup

| Prop          | Type                         | Default        | Description                              |
| ------------- | ---------------------------- | -------------- | ---------------------------------------- |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Controls the layout and connected edges. |
| `as`          | `HTMLTag`                    | `"div"`        | Changes the rendered element.            |

### ButtonGroupText

Accepts `as` and the attributes for that HTML element. Render it as a label
when it describes a form control.

```astro live
---
import { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group"
import { Input } from "@/components/ui/input"
---

<ButtonGroup class="w-full max-w-sm" aria-label="Repository owner">
  <ButtonGroupText as="label" for="repository-name">
    full.dev/
  </ButtonGroupText>
  <Input id="repository-name" value="ui" aria-label="Repository name" />
</ButtonGroup>
```

### ButtonGroupSeparator

Uses the Separator API. Its `orientation` defaults to `"vertical"`.

## Install what this page shows

```bash
npx shadcn@latest add @fulldev/button-group-examples
```
