Button Group
Cluster of buttons (or selects) joined by a shared border, with optional separators and label text.
Overview
ButtonGroup joins multiple Button siblings into a single visual unit by neutralizing inner borders and rounding only the first and last child. Use it for related actions that share a context: copy / paste / cut, alignment, view modes, pagination.
It also accepts Select triggers and ButtonGroupText so you can mix labels and controls in the same border-shared row. ButtonGroupSeparator adds a hairline between groups when you need a visual beat.
Preview
Installation
bash npx gremorie@latest add rx-button-group bash pnpm dlx gremorie@latest add rx-button-group
bash yarn dlx gremorie@latest add rx-button-group
bash bunx --bun gremorie@latest add rx-button-group
Usage
import {
Button,
ButtonGroup,
ButtonGroupSeparator,
} from "@gremorie/rx-forms";
export function Example() {
return (
<ButtonGroup>
<Button variant="outline">Copy</Button>
<ButtonGroupSeparator />
<Button variant="outline">Paste</Button>
</ButtonGroup>
);
}Angular edition planned for Phase 5h. Star the repo to track progress.
API
<ButtonGroup>
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" | Lays children side by side or stacked. Borders adapt: horizontal strips border-l, vertical strips border-t. |
Extends all React.ComponentProps<"div">. Always rendered with role="group" and data-slot="button-group".
<ButtonGroupSeparator>
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | "vertical" | Radix Separator direction. Defaults to a vertical 1px line for horizontal groups. |
Wraps @radix-ui/react-separator. Decorative (forced decorative flag), so it does not announce to screen readers.
<ButtonGroupText>
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as Slot.Root so the first child receives the muted/bordered surface. Use for non-interactive labels (e.g. "of 12", a unit). |
Extends all React.ComponentProps<"div">.
Composition
<ButtonGroup>strips inner borders and rounds only the first / last child via CSS sibling selectors.- Children can be:
<Button>,<Select>triggers (when wrapped inSelectTrigger), or<ButtonGroupText>for labels. <ButtonGroupSeparator>inserts a visible 1px divider between children that share a border.<ButtonGroupText>is the right element for static labels inside the cluster - never use a raw<span>or<div>since the group expects its children to participate in border merging.
Variations
Three actions with separators
Use for related commands. Separators communicate that each action is distinct, even though they share a visual surface.
<ButtonGroup>
<Button variant="outline">Copy</Button>
<ButtonGroupSeparator />
<Button variant="outline">Paste</Button>
<ButtonGroupSeparator />
<Button variant="outline">Cut</Button>
</ButtonGroup>Vertical group for sidebars
Switch orientation to stack actions in a compact column - useful for image editors, video players, and any tool palette.
import { Bold, Italic, Underline } from 'lucide-react';
<ButtonGroup orientation="vertical">
<Button size="icon" variant="outline" aria-label="Bold">
<Bold />
</Button>
<Button size="icon" variant="outline" aria-label="Italic">
<Italic />
</Button>
<Button size="icon" variant="outline" aria-label="Underline">
<Underline />
</Button>
</ButtonGroup>;Pagination with label text
Combine ButtonGroupText with arrow buttons. The text stays non-interactive but inherits the group's surface.
import { ChevronLeft, ChevronRight } from 'lucide-react';
<ButtonGroup>
<Button variant="outline" size="icon" aria-label="Previous page">
<ChevronLeft />
</Button>
<ButtonGroupText>Page 3 of 12</ButtonGroupText>
<Button variant="outline" size="icon" aria-label="Next page">
<ChevronRight />
</Button>
</ButtonGroup>;Accessibility
- Group semantics: the wrapper renders
role="group". Providearia-label(oraria-labelledby) so screen readers announce what the cluster represents. - Keyboard: each child remains an independent focusable element.
TabandShift+Tabmove between them; there is no roving tabindex (useToggleGroupif you want that). - Focus: focused child elevates above its siblings (
z-10) so the focus ring is never clipped by the adjacent button's border. - Separators: rendered as decorative so they do not appear in the accessibility tree.
Related
- Button - the building block
- Toggle Group - same visual idea but with coordinated
aria-pressedstate and roving tabindex - Input Group - the input + addon equivalent
- Select - allowed inside
ButtonGroupwhen you need a dropdown trigger in the cluster