Skip to content

ButtonGroup component

ButtonGroup merges a row of adjacent Buttons into a single visually connected control — shared borders where they touch, radius only on the outer corners, and one set of default props (variant, color, size, radius, fullWidth, isDisabled) that every child inherits unless it sets its own. It's a styling/defaults wrapper, not a selection manager — it doesn't track which child is "active" for you.

Actions11 propsAccessibility notes

Playground

Change a prop on the right and the preview updates. Switch to Code to copy what you see.

Recipes

Copy-paste patterns for the ways ButtonGroup actually gets used — each one a real, self-contained example.

Joined buttons

ButtonGroup joins a set of related actions into one segmented control with shared borders and radius.

<ButtonGroup>
  <Button>Day</Button>
  <Button>Week</Button>
  <Button>Month</Button>
</ButtonGroup>

Shared appearance

Props on the group cascade to every button — set variant, color, and size once instead of per button.

<ButtonGroup variant="bordered" color="primary" size="sm">
  <Button>Bold</Button>
  <Button>Italic</Button>
  <Button>Underline</Button>
</ButtonGroup>

Icon buttons

A group of icon-only buttons makes a compact toolbar — each still needs an aria-label.

<ButtonGroup variant="bordered">
  <Button isIconOnly aria-label="Bold"><BoldIcon /></Button>
  <Button isIconOnly aria-label="Italic"><ItalicIcon /></Button>
  <Button isIconOnly aria-label="Underline"><UnderlineIcon /></Button>
</ButtonGroup>

Full width

fullWidth stretches the group to fill its container, splitting the space evenly — good for a mobile segmented switch.

<ButtonGroup fullWidth variant="bordered">
  <Button>Sign in</Button>
  <Button>Register</Button>
</ButtonGroup>

Sharing defaults across a row of actions

Set variant/color/size/radius once on ButtonGroup instead of repeating them on every Button — any child Button that doesn't set its own value for a given prop inherits the group's, and one that does set its own overrides just that prop while still inheriting the rest.

<ButtonGroup variant="bordered" color="default">
  <Button>Day</Button>
  <Button>Week</Button>
  <Button color="primary">Month</Button>
</ButtonGroup>

Only direct children get group defaults

The inheritance walks React.Children directly — a Button wrapped in another element (a styled <div>, a conditional fragment) won't pick up the group's defaults, since ButtonGroup has no reliable way to know what counts as a "wrapper" versus an unrelated child. Keep Buttons as direct children of ButtonGroup, even if you need conditional rendering around them.

// Works -- both are direct children
<ButtonGroup variant="soft">
  <Button>A</Button>
  {showB && <Button>B</Button>}
</ButtonGroup>

// Doesn't inherit -- Button is nested inside a wrapper, not a direct child
<ButtonGroup variant="soft">
  <div className="wrapper"><Button>A</Button></div>
</ButtonGroup>

Building a segmented toggle

ButtonGroup itself has no concept of a selected item — for a segmented control where exactly one option is active, track the selection in your own state and drive each Button's color/variant off it.

const ViewToggle = () => {
  const [view, setView] = useState<"list" | "grid">("list");
  return (
    <ButtonGroup variant="bordered" size="sm">
      <Button
        color={view === "list" ? "primary" : "default"}
        onPress={() => setView("list")}
      >
        List
      </Button>
      <Button
        color={view === "grid" ? "primary" : "default"}
        onPress={() => setView("grid")}
      >
        Grid
      </Button>
    </ButtonGroup>
  );
};

colorDepth has no real default

Unlike Button, ButtonGroup's colorDepth prop has no built-in default value — it stays unset (letting each child Button fall back to its own default) until you explicitly provide one, at which point it applies to every child that doesn't set colorDepth itself.

Props

The full public API for ButtonGroup. Every prop is stable and intentionally typed.

Content

children

ReactNode

default: —

Buttons rendered inside the group.

Appearance

variant

"solid" | "soft" | "bordered" | "ghost" | "link"

default: solid

Default variant for child Buttons that don't set their own.

color

"default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger"

default: default

Default color for child Buttons that don't set their own.

colorDepth

50 | 100 | ... | 950

default: —

Optional default depth, only applied when provided.

size

"xs" | "xs-sm" | "sm" | "md" | "lg" | "xl"

default: md

Default size for child Buttons that don't set their own.

radius

"none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full"

default: md

Default radius for child Buttons that don't set their own.

Layout & positioning

fullWidth

boolean

default: false

Default fullWidth for child Buttons that don't set their own.

Behavior & states

isDisabled

boolean

default: false

Disables all child Buttons that don't set isDisabled explicitly.

role

string

default: group

ARIA role for the group container.

Styling

className

string

default: —

Class applied to the root element.

style

CSSProperties

default: —

Inline styles for the root element.

Accessibility

  • Renders with role="group" on the wrapper so assistive technology announces the Buttons as a related set, not a series of unrelated controls.
  • Every child stays a real, independently focusable <button> or <a> — ButtonGroup only affects styling and shared default props, never the underlying semantics or tab order of its children.
  • isDisabled on ButtonGroup disables every child that doesn't set isDisabled itself; a child that explicitly sets isDisabled={false} stays enabled even inside a disabled group.

Best practices

  • Use ButtonGroup for actions that are genuinely related (view toggles, pagination, a Cancel/Confirm pair) — for unrelated actions scattered across a toolbar, plain spaced-out Buttons read more clearly.
  • Keep every Button a direct child; if you need conditional rendering, gate the Button itself rather than wrapping it in another element.
  • For a segmented control with a real "selected" state, drive each child's color from your own state — ButtonGroup only shares defaults, it doesn't manage selection.
  • Set fullWidth on the group rather than on each Button individually when you want the whole row to stretch to the container width.