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.

role="group"data-slot="button-group"data-orientation="horizontal"

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

Page

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

3 of 12

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.

OrientationAll childrenLast childSibling children
horizontalrounded-r-nonerounded-r-lgrounded-l-none border-l-0
verticalrounded-b-nonerounded-b-lgrounded-t-none border-t-0

Separator tokens

OrientationSeparator classes
horizontalmx-px w-auto self-stretch bg-input
verticalmy-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-label to the <ButtonGroup>to describe the group purpose (e.g., "Text formatting", "Pagination controls").
  • Focused children receive z-10 via *:focus-visible:z-10 so the focus ring is never clipped by adjacent buttons.
  • data-orientation is 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 ToggleGroup instead.

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>