---
id: "packages/active-ui/button-group"
title: "ButtonGroup"
description: "Buttons that belong together, joined into one control, with text or a separator between them."
url: "/packages/active-ui/button-group"
locale: "en"
revision: "60ab27c225e01d70"
---

# ButtonGroup

A button group joins related actions into one shape: a pager, a split button, a set of zoom
controls. The buttons keep their own behaviour; the group only removes the gaps and inner corners
and gives assistive technology one named group. For buttons that hold a pressed state, such as
text alignment, use a [toggle group](/packages/active-ui/toggle) instead.

**Example: A pager, a split button and a vertical group**

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

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

import {useState} from 'react';
import {ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon} from 'lucide-react';

import {Button} from '@workspace/active-ui/components/primitives/button';
import {
  ButtonGroup,
  ButtonGroupSeparator,
  ButtonGroupText,
} from '@workspace/active-ui/components/primitives/button-group';

const PAGES = 12;

/**
 * Buttons that belong together, joined into one control: a pager with its
 * position as text between the buttons, and a split button whose second
 * half offers more of the same action. The group is `role="group"` and
 * takes the name you give it.
 */
export default function ButtonGroupExample() {
  const [page, setPage] = useState(1);
  return (
    <div className="flex flex-col items-start gap-6">
      <ButtonGroup aria-label="Pages">
        <Button
          variant="outline"
          size="icon"
          aria-label="Previous page"
          disabled={page === 1}
          onClick={() => setPage(page - 1)}
        >
          <ChevronLeftIcon aria-hidden />
        </Button>
        <ButtonGroupText aria-live="polite">
          Page {page} of {PAGES}
        </ButtonGroupText>
        <Button
          variant="outline"
          size="icon"
          aria-label="Next page"
          disabled={page === PAGES}
          onClick={() => setPage(page + 1)}
        >
          <ChevronRightIcon aria-hidden />
        </Button>
      </ButtonGroup>

      <ButtonGroup aria-label="Export">
        <Button>Export</Button>
        <ButtonGroupSeparator />
        <Button size="icon" aria-label="More export options">
          <ChevronDownIcon aria-hidden />
        </Button>
      </ButtonGroup>

      <ButtonGroup orientation="vertical" aria-label="Zoom">
        <Button variant="outline">Zoom in</Button>
        <Button variant="outline">Zoom out</Button>
        <Button variant="outline">Fit</Button>
      </ButtonGroup>
    </div>
  );
}
```

## Usage

```tsx
import {Button} from '@workspace/active-ui/components/primitives/button';
import {
  ButtonGroup,
  ButtonGroupSeparator,
  ButtonGroupText,
} from '@workspace/active-ui/components/primitives/button-group';

<ButtonGroup aria-label="Export">
  <Button>Export</Button>
  <ButtonGroupSeparator />
  <Button size="icon" aria-label="More export options">
    <ChevronDownIcon aria-hidden />
  </Button>
</ButtonGroup>;
```

## Anatomy

- `ButtonGroup`: the frame, `role="group"`. `orientation` stacks the children horizontally (the
  default) or vertically, and joins their borders and corners.
- Any `Button`, `Input`, `SelectTrigger` or nested `ButtonGroup` as children. A nested group keeps
  a gap from its neighbour.
- `ButtonGroupText`: text or an icon between the buttons, on the muted surface, such as the page
  position of a pager. `render` gives it your own element, for example a `<label>`.
- `ButtonGroupSeparator`: a line between two buttons of the same variant, such as the halves of a
  split button, where the joined border would otherwise disappear.

## What your application provides

- An accessible name for the group (`aria-label` or `aria-labelledby`), so a screen reader
  announces what the buttons are for.
- The behaviour of each button, and the accessible name of an icon-only button.
- A live region when text in the group changes as the result of a button, such as the page
  position (`aria-live="polite"` in the example).

## Accessibility and keyboard

- Each button stays its own tab stop; the group adds no arrow-key navigation. For a single choice
  among options, a [toggle group](/packages/active-ui/toggle) or a radio group fits better.
- The group is a `role="group"` container, named by your label.

## API

### ButtonGroup

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `orientation` | `"horizontal" \| "vertical" \| null` | no | The direction of the group: `horizontal` puts the children in a row, `vertical` stacks them. Either way the inner borders and corners are joined. |

### Other parts

**ButtonGroupSeparator**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `orientation` | `Orientation` | no | The orientation of the separator. |
| `render` | `ComponentRenderFn<HTMLProps, SeparatorState> \| 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. |

**ButtonGroupText**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, {}> \| 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. |

**buttonGroupVariants**: `(props?: (ConfigVariants<{ orientation: { horizontal: string; vertical: string; }; }> & ClassProp) \| undefined) => string`

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