ButtonGroup
StableLays out related buttons as one unit, either spaced (an action bar or dialog footer) or attached (a segmented control), and gives the group one accessible name.
When to use
Use it for
- Footers of dialogs, forms and cards: primary at the end, alternatives before it.
- Attached: two to five closely related choices of equal weight, like Unified or Split diff.
Not for
- Switching between views of content: use Tabs, which manage the panel and arrow-key navigation.
- A single choice that persists as a setting: use RadioGroup.
Do and don't
Every example is rendered with the real component, so it always matches what ships.
Put the primary action last
Users read the alternatives on the way to the main action, and the primary lands where the eye ends.
Mix variants in an attached group
An attached group reads as one control. Mixed fills break it into unrelated pieces.
Accessibility
- Renders role="group". Pass aria-label when the buttons are related choices so the group is announced.
- Each button stays in the tab order; the focused button lifts above its neighbors so the ring is never clipped.
- Vertical groups stack the same buttons; the tab order follows the visual order.
Anatomy and tokens
Read from packages/react/src/components/ButtonGroup/ButtonGroup.module.css at build time. Every value is a token: open one to see its
alias chain in both themes.
- Spaced appearance (gap between buttons)
.spaced - Attached appearance (segmented)
.attached.horizontal > *:not(:first-child) - Attached appearance (segmented)
.attached.vertical > *:not(:first-child)
Props
Parsed from the TypeScript types in packages/react/src/components/ButtonGroup/ButtonGroup.tsx, so this table cannot drift from the
code.
It also accepts every native <div> attribute.
| Prop | Type | Default | Description |
|---|---|---|---|
appearance | 'spaced' | 'attached' | 'spaced' | Accessible name for the group, for example "Text alignment". Required when the buttons are related choices. */ 'aria-label'?: string; /** `attached` joins the buttons into one segmented control; `spaced` keeps a gap (action bars). Default `spaced`. |
orientation | 'horizontal' | 'vertical' | 'horizontal' | No description in the code yet. |
align | 'start' | 'end' | 'between' | 'start' | Align the group's buttons. Default `start`. Use `end` for dialog and form footers. |
Playground
Change the props and see the real ButtonGroup update. Controls are generated from the TypeScript props; nothing here changes the system.
<ButtonGroup align="end" aria-label="Review actions" />Workbench
For contributors: every state with the accessibility panel, in the component workbench (Storybook). The stories double as visual test fixtures; the guidance on this page is the user documentation (open ButtonGroup in Storybook) .
Embed Storybook stories here (3)
Loads from https://bridge-storybook.floriangouloubi.com (set STORYBOOK_URL at build time). If Storybook is not running, the frames stay
empty.
Footer
Attached
AllStates
Changelog
- New
Actions: Button gains `danger` variant, `lg` size, `iconStart`, `iconEnd`, `fullWidth` and `render` (a link stays a link); loading keeps focus. New IconButton, ButtonGroup (spaced or attached) and Link (inline, standalone, subtle, external).
Unreleased (current version 0.1.0, no release tagged yet) , Oct 17, 2026