Skip to content

Button Group

Stable

Joins related buttons, inputs and text into a single control so they read as one set of actions.

Anatomy

  1. 1GroupJoins its children into one control: borders merge and inner corners square off. It carries role="group" and should have an aria-label.
  2. 2ActionAny prfct button. Keep one variant across the group so it reads as a unit.
  3. 3SeparatorDivides filled buttons, which have no borders of their own to meet.
  4. 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.json

Usage

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.

https://
.prfct.dev

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.

Do.One variant, one size: the group reads as a single control.
Don’t.Mixed variants look like a mistake and hide which action matters.

Accessibility

  • The group renders role="group". Give it an aria-label (or aria-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.
KeyBehavior
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".

PropTypeDefault
orientation

Direction in which the items are joined.

"horizontal" | "vertical""horizontal"
aria-label

Names the group for assistive technology.

stringNo default
className

Layout classes, e.g. w-full to stretch an input group.

stringNo default

ButtonGroupText

A non-interactive segment for prefixes, suffixes and labels. Renders a <div>; use render to swap the element.

PropTypeDefault
render

Replaces the rendered element while keeping styles.

ReactElement | (props, state) => ReactElementNo default
className

Additional classes.

stringNo default

ButtonGroupSeparator

A hairline between items, for variants without borders. Built on Separator.

PropTypeDefault
orientation

Use horizontal inside vertical groups.

"horizontal" | "vertical""vertical"
className

Override the color on filled buttons, e.g. bg-primary-foreground/30.

stringNo default

buttonGroupVariants

The class generator, exported for custom elements that should join like a group.