Button Group
A container that welds related buttons, inputs, selects and menus into one connected control with shared seams.
- Status
- 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-groupThe 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
Examples
Composition
Use the following composition to build a ButtonGroup:
ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupTextButtonGroup vs ToggleGroup
- Use the
ButtonGroupcomponent when you want to group buttons that perform an action. - Use the
ToggleGroupcomponent when you want to group buttons that toggle 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.
Dropdown Menu
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
| Prop | Type | Default | Description |
|---|---|---|---|
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
ButtonGroupcomponent has theroleattribute set togroup. - 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-labeloraria-labelledbyto label the button group. - The group raises a focused member with
*:focus-visible:relativeand*:focus-visible:z-10, so the global outline frombase.cssis never clipped by the neighbour that overlaps its seam. ButtonGroupSeparatorrenders a Base UISeparator, which isrole="separator"with the rightaria-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.
| Contract | States tested |
|---|---|
| Behaviour | default |
| Accessibility | native-or-base-ui-semantics, browser-accessibility-test |
| Visual | default |
Do / Don't
Deviations
Upstream's file plus packages/ui/upstream/patches/button-group.patch. Every hunk:
- DOC-2 —
cnis imported from@vegastack/design. - API-16 —
"use client"is added:useRendercallsReact.useRefinternally, so the module throws on import in a React Server Component without the boundary.