Skip to main content

Button Group

Displays two or more related buttons as a single, visually joined control. Use for segmented controls, pagination, and view toggles — never to group actions of different severity.

OverviewStyleAccessibility

Description

Anatomy

PartRequired?Notes
Group container Required <wa-button-group> with a required label attribute — this is the group's accessible name (rendered as role="group" with aria-label). Set orientation="vertical" to stack segments instead of the horizontal default.
Button (resting) Required Every child is a Button with variant="neutral" and appearance="plain". Never brand, danger, success, or warning — button groups represent selection state, not action severity.
Button (active / selected) Optional The active segment — in a segmented control, pagination, or toggle pattern — uses appearance="filled" with the same variant="neutral": a solid neutral fill, never a brand colour.
Divider None There isn't one. Both plain and filled appearance have a fully transparent border in the real component — segments are separated only by the active segment's background fill, never by a visible border or line. Don't add a manual divider; it doesn't match what ships.

Variants

States

State Behaviour
Resting (unselected) Transparent background, text-only, transparent border. Adjacent resting segments have no visible boundary between them at all — the label spacing and hover/focus states are the only cues.
Selected / active Solid neutral fill (--wa-color-neutral-fill-normal). Communicates the current selection without borrowing a brand colour.
Hover Resting segments pick up a subtle neutral fill on hover; the already-selected segment darkens slightly. Only one segment can be hovered at a time.
Focus Each button is its own tab stop with the standard focus ring — the group does not use roving tabindex.
Disabled Individual segments can be disabled independently (e.g. a Previous button on page one) while the rest of the group stays interactive.

Usage guidelines

Do / Don't

Do

Keep every child variant="neutral", even the active segment — use appearance="filled" to show selection.

Don't

Use variant="brand" (or any non-neutral variant) on a button-group child to mark it 'selected' — this borrows the semantic action-severity colour. This was a real drift bug found and fixed across Orbit's own Storybook stories in August 2026 (Vertical, Toggle Group, Segmented Control, and With Split were all using variant="brand" on the active segment).

Do

Always set a descriptive label attribute on wa-button-group — it's the group's accessible name.

Don't

Leave the group unlabelled. Screen reader users then hear a sequence of buttons with no shared context.

Layout & Spacing

wa-button-group joins its children flush with no gap. There's no divider line between segments — both plain and filled appearance have a fully transparent border, so the only visual separation is the active segment's background fill. Don't add a manual divider between segments; it doesn't match the real component.

Button height follows the same size scale as Button (xs/s/m/l/xl). Keep every child in a given group the same size.

Tokens

PartTokenValue
Selected background --wa-color-neutral-fill-normal #eeeae4 — solid neutral fill for the active/selected segment (appearance="filled").
Resting background — Transparent (appearance="plain").
Label colour --wa-color-neutral-on-normal Applies to both resting and selected segments — colour alone never carries the selection signal.
Focus ring --wa-color-focus #24a4fc

Engineering notes

  • Built directly on Web Awesome's <wa-button-group> — there is no custom Orbit wrapper component or CSS utility class. Each child is a real Button instance.
  • Every child must be variant="neutral". Set appearance="filled" on the one active/selected segment and appearance="plain" on the rest.
  • wa-button-group does not manage selection state itself — to build a segmented control, toggle which segment has appearance="filled" in JavaScript when a button is clicked. Pair the appearance change with aria-pressed for toggle/segmented-control patterns (Segmented Control, Toggle Group, Sizes), or aria-current="page" for pagination — they're different ARIA widget patterns and aren't interchangeable.
  • Reference implementation: ButtonGroup Storybook stories (Default, Vertical, Sizes, Segmented Control, With Icons, With Split, Toggle Group).

Keyboard interaction

KeyAction
Tab Moves focus to the next button in the group, then out of the group. Each button is its own tab stop — no roving tabindex.
Shift + Tab Moves focus to the previous focusable element.
Enter Activates the focused button.
Space Activates the focused button. For segmented-control buttons with aria-pressed, this toggles/selects the button.

Why it matters

Segmented controls and pagination are used constantly by stakeholder officers and community managers navigating lists and filters — many by keyboard. A selection state that only shows up visually excludes anyone using a screen reader or high-contrast mode. Note that pagination and segmented controls use different ARIA state patterns even though both use the same filled/plain visual convention — see the ARIA table below.

Focus

Each button keeps its own visible focus ring; the group container itself is not focusable. Focus order follows DOM order — first segment to last.

Watch out

Don't rely on the filled background alone to show which segment is selected — pair it with aria-pressed (or an equivalent state) communicated to assistive technology whenever the group acts as a segmented control.

ARIA

Role or attributeWhen to useExample
label (on wa-button-group) Required — describes the group's shared purpose. Becomes the accessible name for the group. label="View mode"
aria-pressed Applied to each button in a segmented-control or toggle pattern (Segmented Control, Toggle Group, Sizes). Set to true on the selected segment, false on the rest, and update it in the same click handler that swaps appearance. aria-pressed="true"
aria-current="page" Applied to the active page number in a pagination pattern instead of aria-pressed — pagination is a different ARIA widget from a segmented control, even though it uses the same filled/plain visual convention. Previous/Next don't get either attribute; they're navigation actions, not selections. aria-current="page"
aria-label (icon-only buttons) Required on every icon-only child — the group's label alone doesn't describe individual segments. aria-label="Grid view"

Watch out

wa-button and wa-input don't have a "prefix"/"suffix" slot — only "start"/"end". An icon assigned to slot="prefix" is silently dropped from render with no console error, which is easy to miss since the button still occupies layout space. This is a real bug that shipped in this component's own Storybook stories (With Icons, With Split) until August 2026 — always verify icon-only buttons actually render, don't just trust the markup.

Things to avoid

  • Using a non-neutral variant on any child to imply selection — it borrows a severity colour that means something else, and doesn't reach users relying on a colour-blind-safe or high-contrast view.
  • Leaving wa-button-group without a label.
  • Managing selection with only a background-colour change and no aria-pressed update.

Was this page helpful?

Updated 5 October 2026