Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Button Group

A container that welds related buttons, inputs, selects and menus into one connected control with shared seams.

Status
stable
Since
0.10.0
Accessibility pattern
labelled group role

Last updated

Install

Add Button Group from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/button-group

The same command installs the registry items it composes: @vegastack/separator.

Usage

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

<ButtonGroup>
  <Button>Button 1</Button>
  <Button>Button 2</Button>
</ButtonGroup>;

Anatomy

ButtonGroup — data-slot="button-group"
ButtonGroupSeparator — data-slot="button-group-separator"
ButtonGroupText

Examples

Composition

Use the following composition to build a ButtonGroup:

ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupText
https://

ButtonGroup vs ToggleGroup

  • Use the ButtonGroup component when you want to group buttons that perform an action.
  • Use the ToggleGroup component when you want to group buttons that toggle a state.
ButtonGroup — each button performs an action
ToggleGroup — each button toggles a state

Orientation

Set the orientation prop to change the button group layout. The horizontal group flattens the inline-end corners and drops the inline-start border of every button after the first; the vertical group does the same along the block axis.

Size

Control the size of buttons using the size prop on individual buttons — the group owns the seams, never the height.

Nested

Nest ButtonGroup components to create button groups with spacing: a group whose children are groups picks up gap-2, so the seams stay welded inside each inner group and open up between them.

Separator

The ButtonGroupSeparator component visually divides buttons within a group.

Buttons with variant outline do not need a separator since they have a border. For other variants, a separator is recommended to improve the visual hierarchy.

Split

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

Input

Wrap an Input component with buttons. The group stretches the input with flex-1, so the button keeps its intrinsic width.

Input Group

A button group welds to an InputGroup exactly as upstream's does: the group owns the shared seams, InputGroup owns the bordered field surface and its addons.

Create a split button group with a DropdownMenu component. Render the trigger through a Button so the menu inherits the group's seam.

Select

Pair with a Select component. The group gives the trailing trigger back its inline-end radius, so a select at the end of a row does not read as clipped.

Popover

Use with a Popover component — the same split-action shape as the dropdown, with a form inside the surface instead of a menu.

RTL

Every seam is written in logical properties (rounded-e-*, rounded-s-*, border-s-0), so a right-to-left document mirrors the welded corners and the dropped borders with no extra work.

API Reference

PropTypeDefaultDescription
orientation"horizontal" | "vertical"

ButtonGroupSeparator adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

ButtonGroupText adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

Accessibility

  • The ButtonGroup component has the role attribute set to group.
  • Use Tab to navigate between the buttons in the group. The group is not a roving-focus widget: every member is its own tab stop.
  • Use aria-label or aria-labelledby to label the button group.
  • The group raises a focused member with *:focus-visible:relative and *:focus-visible:z-10, so the global outline from base.css is never clipped by the neighbour that overlaps its seam.
  • ButtonGroupSeparator renders a Base UI Separator, which is role="separator" with the right aria-orientation — it is announced as a divider, not as another control.
  • An icon-only button inside a group still has no visible text; give it an aria-label.
ContractStates tested
Behaviourdefault
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test
Visualdefault

Do / Don't

Do
Give every group an aria-label, and use ButtonGroup for actions and ToggleGroup for state.
Don't
Hand-roll the seam with negative margins — the group owns the radius and border rules.

Deviations

Upstream's file plus packages/ui/upstream/patches/button-group.patch. Every hunk:

  • DOC-2cn is imported from @vegastack/design.
  • API-16"use client" is added: useRender calls React.useRef internally, so the module throws on import in a React Server Component without the boundary.

On this page