Button Group
Joins related buttons into one segmented control. Horizontal or vertical, in three sizes, with an optional detached spacing mode.
Playground
<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.jsonUsage
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.
<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
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | "horizontal" | "vertical" | "horizontal" | Row or column. Controls which edges are joined and which corners stay rounded in the attached layout. |
| detached | boolean | false | Skip the joined styling and space the children with a size-tuned gap instead. Buttons inside attached groups also get a stronger active state. |
| wrapLayout | boolean | false | Allow 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. |
| Component | Description |
|---|---|
| ButtonGroupText | Static 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. |
| ButtonGroupSeparator | Thin 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>