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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Buttons rendered inside the group. |
children
ReactNode
default: —
Buttons rendered inside the group.
Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "solid" | "soft" | "bordered" | "ghost" | "link" | solid | Default variant for child Buttons that don't set their own. |
| color | "default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger" | default | Default color for child Buttons that don't set their own. |
| colorDepth | 50 | 100 | ... | 950 | — | Optional default depth, only applied when provided. |
| size | "xs" | "xs-sm" | "sm" | "md" | "lg" | "xl" | md | Default size for child Buttons that don't set their own. |
| radius | "none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full" | md | Default radius for child Buttons that don't set their own. |
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
| Prop | Type | Default | Description |
|---|---|---|---|
| fullWidth | boolean | false | Default fullWidth for child Buttons that don't set their own. |
fullWidth
boolean
default: false
Default fullWidth for child Buttons that don't set their own.
Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| isDisabled | boolean | false | Disables all child Buttons that don't set isDisabled explicitly. |
| role | string | group | ARIA role for the group container. |
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
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Class applied to the root element. |
| style | CSSProperties | — | Inline styles for the root element. |
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.