Button Group

Button Group

Joins related buttons into one segmented control. Horizontal or vertical, in three sizes, with an optional detached spacing mode.

Playground

variant
orientation
size
detached
<ButtonGroup>
  <Button variant="outline">Left</Button>
  <Button variant="outline">Center</Button>
  <Button variant="outline">Right</Button>
</ButtonGroup>

Preview

Put outline buttons side by side inside a ButtonGroup and they fuse into one segmented control – inner corners squared, shared edges collapsed.

<ButtonGroup>
  <Button variant="outline">Left</Button>
  <Button variant="outline">Center</Button>
  <Button variant="outline">Right</Button>
</ButtonGroup>

Installation

npx shadcn@latest add https://espresso-base-ui.vercel.app/r/button-group.json

Usage

import {
  ButtonGroup,
  ButtonGroupSeparator,
  ButtonGroupText,
} from "@/components/ui/button-group"
<ButtonGroup>
  <Button variant="outline">Save</Button>
  <Button variant="outline" size="icon" aria-label="More save options">
    <ChevronDown />
  </Button>
</ButtonGroup>

With text and icons

Mix icon buttons with a ButtonGroupText insert for stepper- or toolbar-style controls; drop in a ButtonGroupSeparator to divide segments explicitly.

10
<ButtonGroup>
  <Button variant="outline" size="icon" aria-label="Decrease">
    <Minus />
  </Button>
  <ButtonGroupText className="bg-transparent">10</ButtonGroupText>
  <Button variant="outline" size="icon" aria-label="Increase">
    <Plus />
  </Button>
</ButtonGroup>

<ButtonGroup>
  <Button variant="secondary" size="icon" aria-label="Bold">
    <Bold />
  </Button>
  <ButtonGroupSeparator />
  <Button variant="secondary" size="icon" aria-label="Italic">
    <Italic />
  </Button>
  <ButtonGroupSeparator />
  <Button variant="secondary" size="icon" aria-label="Underline">
    <Underline />
  </Button>
</ButtonGroup>

Vertical

orientation="vertical" stacks the segments and joins them along the horizontal edges instead.

<ButtonGroup orientation="vertical">
  <Button variant="outline">Top</Button>
  <Button variant="outline">Middle</Button>
  <Button variant="outline">Bottom</Button>
</ButtonGroup>

Sizes

size on the group overrides the height, padding and typography of every child button, so you size the control in one place.

<ButtonGroup size="sm">
  <Button variant="outline">Small</Button>
  <Button variant="outline">Group</Button>
</ButtonGroup>

<ButtonGroup size="default">
  <Button variant="outline">Default</Button>
  <Button variant="outline">Group</Button>
</ButtonGroup>

<ButtonGroup size="lg">
  <Button variant="outline">Large</Button>
  <Button variant="outline">Group</Button>
</ButtonGroup>

Detached

detached keeps each button standalone with a size-tuned gap – useful for related-but-separate action rows; add wrapLayout to let a long row wrap onto multiple centered lines.

<ButtonGroup detached>
  <Button>Default</Button>
  <Button>Default</Button>
  <Button>Default</Button>
</ButtonGroup>

<ButtonGroup detached wrapLayout size="sm">
  <Button>Button</Button>
  <Button>Button</Button>
  <Button>Button</Button>
  <Button>Button</Button>
  <Button>Button</Button>
</ButtonGroup>

API reference

ButtonGroup styles its children purely through CSS selectors keyed on their data-slot attributes, so Buttons, dropdown/popover/tooltip triggers, selects and inputs all join seamlessly. All parts render a div and accept className plus standard div props.

ButtonGroup

PropTypeDefaultDescription
orientation"horizontal" | "vertical""horizontal"Row or column. Controls which edges are joined and which corners stay rounded in the attached layout.
detachedbooleanfalseSkip the joined styling and space the children with a size-tuned gap instead. Buttons inside attached groups also get a stronger active state.
wrapLayoutbooleanfalseAllow the group to wrap onto multiple lines with centered rows. Pair with detached.
size"sm" | "default" | "lg""default"Overrides child Button height, padding and typography (28px / native / 40px) and sets the detached gap.
ComponentDescription
ButtonGroupTextStatic text cell that sits flush between segments (data-slot="button-group-text"), e.g. the value in a stepper. Renders a div; use render to swap the element.
ButtonGroupSeparatorThin divider between segments (data-slot="button-group-separator"). A vertical Separator by default; set orientation="horizontal" inside vertical groups.

Accessibility & styling hooks

The root renders role="group", so assistive tech announces the buttons as one related cluster – add an aria-label when the grouping needs a name. It exposes data-slot="button-group" and reflects its props as data-orientation, data-size, and data-detached / data-wrap (present only when true) – Button itself reads these to adjust its radius and active state inside attached groups. The buttonGroupVariants cva helper is also exported for building custom grouped surfaces:

import { buttonGroupVariants } from "@/components/ui/button-group"

<div className={cn(buttonGroupVariants({ orientation: "vertical" }))}>
  ...
</div>

Search documentation

Search the component docs