Joins related Buttons into one visual unit with shared borders and rounded outer corners only, horizontal or vertical. Also the base for a split button and an icon toolbar.
import { Button, ButtonGroup } from "@rdloom/react";
export default function ButtonGroupBasicExample() {
return (
<div className="flex w-full justify-center">
<ButtonGroup label="Calendar view">
<Button variant="secondary">Day</Button>
<Button variant="secondary">Week</Button>
<Button variant="secondary">Month</Button>
</ButtonGroup>
</div>
);
}Installation
npx rdloom add button-groupCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/button-group.jsonWorks, but without upgrade tracking.
Usage
import { Button, ButtonGroup } from "@rdloom/react";
<div className="flex w-full justify-center">
<ButtonGroup label="Calendar view">
<Button variant="secondary">Day</Button>
<Button variant="secondary">Week</Button>
<Button variant="secondary">Month</Button>
</ButtonGroup>
</div>Split button
import { Button, ButtonGroup, Menu, MenuItem, MenuTrigger } from "@rdloom/react";
export default function ButtonGroupSplitButtonExample() {
return (
<div className="flex w-full justify-center">
<ButtonGroup label="Publish">
<Button variant="primary">Publish</Button>
<MenuTrigger>
<Button variant="primary" aria-label="More publish options" className="px-2">
<svg aria-hidden="true" viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M4 6l4 4 4-4" />
</svg>
</Button>
<Menu placement="bottom end">
<MenuItem id="schedule">Schedule for later</MenuItem>
<MenuItem id="draft">Save as draft</MenuItem>
<MenuItem id="preview">Preview</MenuItem>
</Menu>
</MenuTrigger>
</ButtonGroup>
</div>
);
}Icon toolbar
import { Button, ButtonGroup } from "@rdloom/react";
const icon = { "aria-hidden": true, viewBox: "0 0 16 16", width: 16, height: 16, fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" } as const;
export default function ButtonGroupIconToolbarExample() {
return (
<div className="flex w-full justify-center">
<ButtonGroup label="Text alignment" size="sm">
<Button variant="secondary" aria-label="Align left" className="aspect-square px-0!">
<svg {...icon}><path d="M2 4h12M2 8h8M2 12h10" /></svg>
</Button>
<Button variant="secondary" aria-label="Align center" className="aspect-square px-0!">
<svg {...icon}><path d="M2 4h12M4 8h8M3 12h10" /></svg>
</Button>
<Button variant="secondary" aria-label="Align right" className="aspect-square px-0!">
<svg {...icon}><path d="M2 4h12M6 8h8M4 12h10" /></svg>
</Button>
</ButtonGroup>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
labelrequiredAccessible name of the group, e.g. "Text formatting". Read when focus enters the group. | string | none |
orientationDirection the buttons are stacked. | "horizontal" | "vertical" | "horizontal" |
sizeSize passed to every Button that does not set its own. A Button inside a MenuTrigger needs its own size. | "sm" | "md" | "lg" | none |
childrenTwo or more Buttons, or a MenuTrigger holding a Button. | node | none |
Accessibility
Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Tab moves through the buttons one by one, in order
- Enter or Space activates the focused button
- A menu button opens its menu with Enter, Space or Arrow Down
Screen readers announce
- Announced as a group with its label, then each button by name
- A menu button is announced as a menu button, collapsed or expanded
What your code must do
- The group has role group and an accessible name from label
- Every button keeps its own accessible name; an icon-only button needs an aria-label
- The focused button is raised above its neighbours so its focus ring is never covered by a shared border
- Buttons keep their own disabled and pressed states; the group does not hide them
Guidelines
Use it when
- Closely related actions that belong together, like Bold, Italic, Underline
- A split button: a main action next to a menu of alternatives
- Previous and next style controls
Avoid it when
- Choosing one option from several: use SegmentedControl or ToggleButtonGroup
- Unrelated actions: leave space between them
- Many buttons that do not fit in one row: use a Menu
Don't
- Mixing several filled primary buttons in one group
- Icon-only buttons without an aria-label
- A split button whose menu item repeats the main action
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-border-default--rd-color-border-strong--rd-color-surface-default--rd-color-surface-subtle--rd-color-action-primary--rd-color-focus-ring--rd-radius-control