Button Group
A row of related actions that allows at most one primary. A single button needs no group, and a set of choices between values is a radio button group, not this.
Live preview
Rendered by the component itself from the same generated tokens as the rest of the site.
<ButtonGroup>
<Button variant="ghost">Cancel</Button>
<Button>Save draft</Button>
<Button variant="primary">Publish</Button>
</ButtonGroup>Installation
Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.
npx shadcn@latest add https://www.graphite-ui.com/r/button-group.jsonimport { ButtonGroup } from '@/components/ui/button-group'Anatomy
One slot, two or more Buttons. The group adds a gap from the spacing scale and no colour of its own: every visual decision belongs to the buttons inside it.
- ActionsRequiredTwo or more Buttons. A group of one is a Button, and needs no wrapper.
API reference
Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.
| Prop | Type | Default | Notes |
|---|---|---|---|
| className | — | — | Merged after the group class, so a caller can extend without forking. |
Design tokens
Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.
spacingGap between actions.
Usage
The contract's prohibitions, written as the choices you will actually face.
Do
- Wrap every footer or toolbar in a group, so the one-primary rule is checked instead of remembered.
- Pass the Buttons as direct children or inside a fragment. Fragments are unwrapped before the count, so a footer passed as <>…</> is still checked.
- Leave the group with no primary when no action is clearly the main one.
- Stack actions vertically in the container’s own layout when a narrow space needs it.
Don’t
- Add a second primary. It throws at render, naming the count, and that is the intended outcome.
- Wrap a Button in a div or span. The check does not look inside other elements, so a second primary in there slips past it and the rule is still broken.
- Override the gap on one instance. Two footers in one product should not disagree about spacing.
- Use a group for a single button. A group of one is just a Button.
Accessibility
What the component does for you, and what it leaves to you.
- Roles
- A plain div with no role. When the actions need a shared name, pass role="group" and an aria-label: both spread to the element.
- Keyboard
- Each Button stays a separate tab stop in source order. The group adds no arrow-key navigation and traps nothing.
- Order
- The row never reverses, so the order a screen reader announces is the order a sighted reader sees.
- Focus
- Each Button draws its own focus ring. The group draws nothing and clips nothing.
- Emphasis
- With at most one primary, a reader scanning by visual weight finds one main action, not two competing ones.
