Joined row of buttons that share a single bordered surface for segmented toolbars and view switchers.
Description
ButtonGroup is a compound component that paints a single joined surface around a row of ButtonGroupItems. The root renders as a <div role="group">; each item renders as a <button> (or any element via asChild), and the group's shared border, outer radius, and shadow give the cluster a unified look while individual hover, active, and focus states stay per-item.
Reach for it when several actions belong together visually: segmented view switchers (list / grid / gallery), inline action clusters in tables and headers, paging controls, formatting toolbars, or any place a single bordered surface communicates "these options are siblings." The root broadcasts variant and size through context so every item picks up the same sizing without repeating props on the children.
Don't use ButtonGroup for a single standalone action; reach for Button directly. If the user is picking one option out of several and the choice should persist visibly, prefer ToggleGroup (or Tabs when the labels also switch sibling views). For an overflow menu of unrelated actions that don't all need to be visible, use DropdownMenu.
Installation
Anatomy
Usage
Examples
Variants
variant sets the visual tone of the joined surface and cascades to every item via context. primary highlights the active item with the brand color, neutral is the high-contrast option for dark active surfaces, and soft keeps the cluster quiet for dense toolbars.
Shape
shape controls the outer radius of the group. rounded (default) follows the size's radius scale, pill is fully rounded for compact switchers, and square leaves the corners flush, which is useful when the group sits against another bordered surface.
Sizes
size accepts xs, sm, md, lg, and xl. The size is set on the root and inherited by every item; setting size on an individual ButtonGroupItem is supported but rarely needed.
With Icon
Each item accepts leadingIcon and trailingIcon. The wrappers carry data-slot="button-group-item-icon" and size themselves to match the active size, so icons stay aligned with the label across the full size scale.
Icon Only
iconOnly collapses an item to a square footprint with no horizontal padding, the standard pattern for formatting toolbars. Always pair it with an aria-label so screen readers announce the action.
States
Items expose three state props: active (writes data-active="true" and aria-pressed), loading (swaps the trailing slot for a spinner and sets aria-busy), and disabled (sets aria-disabled plus the native disabled attribute). Use active for the currently-selected segment in a view switcher.
Composition
ButtonGroupItem accepts asChild to render any element (commonly an <a>) while keeping the joined surface and state styling. Items also forward children into a flex slot, so you can nest a Badge for counts inline.
Accessibility
ButtonGroup writes role="group" on the root so assistive tech announces the cluster as a single related group of controls. Each ButtonGroupItem is a real <button> (or whatever element you pass via asChild), so focus, activation, and keyboard semantics come from the underlying element.
ARIA notes:
- The root sets
role="group"; pass anaria-labeloraria-labelledbyon theButtonGroupwhen the cluster needs a name beyond its visible content. activewritesaria-pressed="true"on the item so the selected segment is announced as a toggle state. The attribute is omitted entirely whenactiveisfalse.loadingwritesaria-busy="true"and suppresses pointer events while the spinner is visible.disabledmirrors toaria-disabledand the nativedisabledattribute, so the item is removed from tab order.- When using
iconOnly, always providearia-labelon the item; the icon alone is not announced.
Styling
Tailwind override: pass className to merge Tailwind classes with the component's CVA classes (via cn()):
Data slots and attributes: the component sets these for CSS targeting:
data-slot="button-group"on the root, withdata-variant="<variant>",data-shape="<shape>",data-size="<size>", androle="group".data-slot="button-group-item"on each item, withdata-variant,data-size, anddata-active="true"(only present whenactive).data-slot="button-group-item-icon"on each leading / trailing icon wrapper.data-slot="button-group-item-content"on the inner text/children wrapper.data-slot="button-group-item-spinner"on the loading spinner SVG.
Target a specific state in CSS:
Related Components
- Button: use this for a single, standalone action that doesn't need to sit inside a joined surface.
- ToggleGroup: mutually-exclusive selection where one option stays visibly highlighted.
- Tabs: when the labels act as primary navigation between sibling views.
- DropdownMenu: overflow menu for unrelated actions that don't all fit inline.
API Reference
ButtonGroup
Compound root that paints the joined surface and broadcasts variant and size to every ButtonGroupItem via context. Sets role="group" and writes data-slot="button-group" plus data-variant, data-shape, and data-size on the root. Extends React.ComponentProps<"div">, so any standard div attribute (id, aria-label, onClick, etc.) is accepted.
Props
Variants
ButtonGroupItem
A single segment. Renders as a <button> by default, or as any element when asChild is true (via Radix's Slot). Inherits variant and size from the surrounding ButtonGroup unless overridden. Writes data-slot="button-group-item" plus data-variant, data-size, and data-active, and forwards aria-pressed / aria-busy / aria-disabled from active / loading / disabled. Extends React.ComponentProps<"button">, so any standard button attribute is accepted.