/// Inputs
ButtonGroup
A horizontal cluster of Buttons or IconButtons — seamed into one control by default, or spaced by a gap.
import { ButtonGroup } from "@protocore/pds";Basics
By default the group is attached: the members butt together and their shared inner borders collapse into a single seam, reading as one segmented control. Pass an aria-label to name the cluster.
Detached
Set attached={false} for related-but-separate actions — a primary/secondary CTA pair, say. The gap prop (sm · md · lg) controls the spacing.
Icon toolbar
Attached IconButtons make a compact toolbar. This is the workhorse pattern for editor controls and table row actions.
When to use
Use ButtonGroup to visually associate a small set of *independent* actions — each member still fires its own onClick.
- Choosing one option from a mutually-exclusive set (a view mode, a time range)? That's a selection, not an action — use SegmentedControl or RadioGroup so the pressed state is modelled correctly.
- Paging through results? Use Pagination, which handles the numbering and boundaries for you.
- A group is a layout wrapper, not a roving-focus widget: every button inside stays independently tabbable.
Do & don't
Do
- Group actions that genuinely belong together — copy / export / archive on one record.
- Give the group an aria-label describing the set ("Row actions").
- Keep the members the same variant and size so the seam reads cleanly.
- Use attached={false} with a gap when a CTA pair shouldn't look like one control.
Don't
- Don't use it to pick one option from a set — reach for SegmentedControl or RadioGroup.
- Don't mix variants inside an attached group; the seam and heights will clash.
- Don't cram unrelated actions together just because they're adjacent.
- Don't nest ButtonGroups — flatten to a single row instead.
Accessibility
- Renders a `role="group"` container; pass `aria-label` (or `aria-labelledby`) to name it.
- Each child button keeps its own focus and activation — Tab moves between members.
- For single-select semantics (aria-pressed / roving focus) use SegmentedControl or RadioGroup instead.
- Visual seaming is purely CSS; it doesn't change the members' roles or tab order.
ButtonGroup props
| Prop | Type | Default | Description |
|---|---|---|---|
attached | boolean | true | Butt the buttons together and collapse their shared inner borders into one seam. Defaults to `true`. |
className | string | — | |
gap | enum | sm | Space between members when not `attached`. Ignored while `attached`. |
style | CSSProperties | — |