Description
Groups two or more related buttons into a single, visually joined control — no gap between segments, flush edges.
Segmented controls (view-mode switches, status filters), pagination, toolbars, and export/format pickers — for example, the engagement list's view switcher or a table's pagination row.
Loose buttons placed near each other don't read as related. ButtonGroup collapses the gap so users see one control surface with multiple segments, not several unrelated actions.
Built directly on Web Awesome's <wa-button-group>, wrapping one or more Button instances. There is no separate Orbit wrapper component — it's the real wa-button-group element, styled with Orbit's theme.
Anatomy
| Part | Required? | 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
Equal-weight group
All segments equal weight — nothing selected.
Toolbar actions or format choices with no default — icon-only layout switches, export options.
Segmented control
One segment filled to show the current selection, the rest plain.
Mutually exclusive filters or views — status filters, view-mode switches.
Pagination
Current page filled; Previous/Next stay plain since they're navigation actions, not selections.
Paged tables and lists.
Toggle group
Simulates a toggle by moving the filled state to whichever segment is active.
View switchers where exactly one option is always active (List / Grid / Map).
Vertical
Same rules, stacked orientation.
Sidebars and narrow layouts where a horizontal group would wrap.
With split action
A primary action joined to a menu trigger. Still variant="neutral" throughout — the group styling communicates 'joined', not 'important'.
A default action with secondary options behind a dropdown, visually anchored to it (e.g. Export ▾).
Sizes
Every child in a group shares one size — xs/s/m/l/xl, matching Button's own scale.
Match the size of surrounding controls; don't mix sizes within a single group.
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
When to use
- Segmented controls — mutually exclusive view or filter switches (List / Map / Table).
- Pagination controls.
- Equal-weight action sets — export format choices, toolbar actions.
When not to use
- Don't use for navigation between unrelated pages or sections — use tabs or a nav component instead.
- Don't mix variants within a group. Every child stays
variant="neutral"; if you need a colour-coded destructive action, pull it out as a standalone Button next to the group, not inside it. - Don't use more than four or five segments — beyond that, switch to a Select.
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
| Part | Token | Value |
|---|---|---|
| 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". Setappearance="filled"on the one active/selected segment andappearance="plain"on the rest. wa-button-groupdoes not manage selection state itself — to build a segmented control, toggle which segment hasappearance="filled"in JavaScript when a button is clicked. Pair the appearance change witharia-pressedfor toggle/segmented-control patterns (Segmented Control, Toggle Group, Sizes), oraria-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
| Key | Action |
|---|---|
| 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 attribute | When to use | Example |
|---|---|---|
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-groupwithout alabel. - Managing selection with only a background-colour change and no
aria-pressedupdate.