Skip to content
Protocore Design Systemv1.6.9

/// 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";
View as Markdown

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

PropTypeDefaultDescription
attachedbooleantrueButt the buttons together and collapse their shared inner borders into one seam. Defaults to `true`.
classNamestring
gapenumsmSpace between members when not `attached`. Ignored while `attached`.
styleCSSProperties

Related

  • ButtonThe system action control — mono uppercase label, sharp corners, and a primary fill that inverts on hover.
  • IconButtonA square, icon-only action control — Button's affordances with a required aria-label.
  • SegmentedControlCompact single-select toggle — a bordered row of mono uppercase segments where the active one inverts to a solid fill.
  • PaginationPage navigation — a mono button row. Numbered mode draws pages with ellipses and an inverted current page; cursor mode draws prev/next only.