ButtonGroup
Groups multiple buttons into a single visual unit with shared borders and merged radii. Supports horizontal and vertical orientation, visual separators, non-interactive text labels, and composable patterns like split buttons and toolbars. Built with CSS-only border/radius merging via data-slot selectors.
Anatomy
The <ButtonGroup> container renders a <div> with role="group". It uses data-slot="button-group" and data-orientation attributes for CSS selection. Child buttons automatically have their intermediate border-radii removed and shared borders collapsed via sibling selectors targeting [data-slot] elements.
Orientation
Set orientation="vertical" to stack buttons vertically. Horizontal is the default. The group adjusts its border-radius and border-collapse logic automatically based on orientation.
Horizontal (default)
Inline actions, pagination, segmented controls
Vertical
Stacked menus, sidebar actions, mobile layouts
Sub-components
Two optional sub-components enhance the group: <ButtonGroupSeparator> adds a visual divider between buttons, and <ButtonGroupText> renders a non-interactive label that participates in the border merging via useRender.
ButtonGroupSeparator
Visual divider between split actions
ButtonGroupText
Non-interactive label via useRender
Composition Patterns
ButtonGroup is a layout primitive — compose it with different button configurations to build common UI patterns.
Split button
Pagination controls
Formatting toolbar
Segmented control
States
Individual buttons within a group maintain their own state. Disabled buttons remain in the group layout. Focused buttons get z-10 via *:focus-visible:z-10 to ensure the focus ring overlaps adjacent buttons.
Disabled button in group
Focused button
Mixed variants
Token Map
CSS class mappings applied by orientation for border-radius collapsing and border removal on adjacent children.
| Orientation | All children | Last child | Sibling children |
|---|---|---|---|
horizontal | rounded-r-none | rounded-r-lg | rounded-l-none border-l-0 |
vertical | rounded-b-none | rounded-b-lg | rounded-t-none border-t-0 |
Separator tokens
| Orientation | Separator classes |
|---|---|
horizontal | mx-px w-auto self-stretch bg-input |
vertical | my-px h-auto self-stretch bg-input |
Design Guidelines
Do
- Group related actions. Only buttons that operate on the same context or object should share a group.
- Keep groups small (2-5 items). Large groups become hard to scan. Split into multiple groups separated by whitespace if needed.
- Use consistent variants within a group. Mixing primary and outline in the same group creates visual noise — use a split button pattern instead.
- Add aria-label to the group. The
role="group"container should have an accessible label describing the group purpose.
Don't
- Don't group unrelated actions. "Save" and "Help" shouldn't share a group — they operate on different contexts.
- Don't exceed 5 buttons in a group. Beyond 5 items, consider a dropdown, menu, or toolbar with multiple groups.
- Don't nest ButtonGroups. Nested groups create confusing border collisions. Place groups side by side with a gap instead.
- Don't mix orientations in the same group. Pick horizontal or vertical — mixing creates broken border merging.
Developer Reference
Accessibility
- The container uses
role="group"to communicate the grouping relationship to assistive technologies. - Add
aria-labelto the<ButtonGroup>to describe the group purpose (e.g., "Text formatting", "Pagination controls"). - Focused children receive
z-10via*:focus-visible:z-10so the focus ring is never clipped by adjacent buttons. data-orientationis exposed on the container, allowing CSS and JS consumers to detect the layout direction.- Focus management is left to the browser — Tab moves through each button sequentially. For arrow-key navigation (roving tabindex), use
ToggleGroupinstead.
Usage
import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from "@/components/ui/button-group"
import { Button } from "@/components/ui/button"
// Basic horizontal group
<ButtonGroup aria-label="Actions">
<Button variant="outline">Left</Button>
<Button variant="outline">Center</Button>
<Button variant="outline">Right</Button>
</ButtonGroup>
// Vertical group
<ButtonGroup orientation="vertical" aria-label="Options">
<Button variant="outline">Top</Button>
<Button variant="outline">Middle</Button>
<Button variant="outline">Bottom</Button>
</ButtonGroup>
// Split button with separator
<ButtonGroup aria-label="Save options">
<Button>Save</Button>
<ButtonGroupSeparator />
<Button size="icon">
<ChevronDown />
</Button>
</ButtonGroup>
// With text label
<ButtonGroup aria-label="Pagination">
<Button variant="outline" size="icon"><ChevronLeft /></Button>
<ButtonGroupText>3 of 12</ButtonGroupText>
<Button variant="outline" size="icon"><ChevronRight /></Button>
</ButtonGroup>
// Formatting toolbar with multiple groups
<div className="flex items-center gap-2">
<ButtonGroup aria-label="Text style">
<Button variant="outline" size="icon"><Bold /></Button>
<Button variant="outline" size="icon"><Italic /></Button>
<Button variant="outline" size="icon"><Underline /></Button>
</ButtonGroup>
<ButtonGroup aria-label="Text alignment">
<Button variant="outline" size="icon"><AlignLeft /></Button>
<Button variant="outline" size="icon"><AlignCenter /></Button>
<Button variant="outline" size="icon"><AlignRight /></Button>
</ButtonGroup>
</div>