Button Group
StableJoins related buttons, inputs and text into a single control so they read as one set of actions.
Anatomy
- 1GroupJoins its children into one control: borders merge and inner corners square off. It carries role="group" and should have an aria-label.
- 2ActionAny prfct button. Keep one variant across the group so it reads as a unit.
- 3SeparatorDivides filled buttons, which have no borders of their own to meet.
- 4Menu buttonOpens related options — the split-button pattern. An icon-only button, so it needs an aria-label.
Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/button-group.jsonUsage
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"<ButtonGroup aria-label="Message actions">
<Button variant="outline">Archive</Button>
<Button variant="outline">Snooze</Button>
<Button variant="outline">Flag</Button>
</ButtonGroup>The group squares off the inner corners of its children and collapses their shared borders. It works with anything that carries a data-slot — buttons, inputs, select triggers, dropdown triggers and ButtonGroupText.
Examples
Orientation
orientation="vertical" stacks the items and joins them top to bottom — the classic zoom control.
With separators
Filled variants have no borders to share, so add ButtonGroupSeparator between items to keep them distinct.
With inputs
Attach a button to an input for search and copy fields, and use ButtonGroupText for fixed prefixes and suffixes like protocols and domains.
Split button
Pair a primary action with a menu of alternatives. The main button runs the most common option; the chevron opens the rest.
<ButtonGroup>
<Button>Merge pull request</Button>
<ButtonGroupSeparator className="bg-primary-foreground/30" />
<DropdownMenu>
<DropdownMenuTrigger render={<Button size="icon" aria-label="More merge options" />}>
<ChevronDownIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">…</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>Nested groups
Groups nested inside a group keep their own joined edges and are spaced apart, which is how toolbars cluster related tools.
Guidelines
When to use
- For a small set of related, equally weighted actions on the same object: Archive, Snooze, Flag.
- To attach an action to an input, like search or copy.
- For split buttons, where one action is the default and the others are variations of it.
When not to use
- To let people choose between options — use a Toggle Group, which tracks which item is selected.
- For actions of different importance. A group implies equal weight; a primary action and a cancel button belong in separate buttons with space between them.
- For long lists of actions. Past four or five items, move the rest into a Dropdown Menu.
Keep variants consistent
Every item in a group should share one variant and one size. Mixed variants break the joined silhouette that makes a group read as one control.
Accessibility
- The group renders
role="group". Give it anaria-label(oraria-labelledby) that names the set — Message actions, Map zoom — so screen readers announce the context when focus enters it. - Each child keeps its own semantics and its own tab stop. Unlike a Toggle Group, there is no roving focus: buttons in a group are independent actions.
- Icon-only buttons inside a group still need an
aria-label. - Focused items are raised above their neighbors, so the focus ring is never clipped by an adjacent border.
| Key | Behavior |
|---|---|
TabShiftTab | Moves focus between the buttons, inputs and triggers in the group. |
EnterSpace | Activates the focused button. |
API reference
ButtonGroup
Renders a <div> with role="group" and data-slot="button-group".
ButtonGroupText
A non-interactive segment for prefixes, suffixes and labels. Renders a <div>; use render to swap the element.
ButtonGroupSeparator
A hairline between items, for variants without borders. Built on Separator.
buttonGroupVariants
The class generator, exported for custom elements that should join like a group.