ButtonGroup

Updated: Sep 24, 2026
ButtonGroup arranges a small set of button-like controls in a horizontal row that always fits. It owns the row layout, the alignment of the set as a whole, the row height, the spacing compensation that keeps collapsed and expanded buttons evenly placed, and left/right focus navigation between children.
Use it for compact decisions — a confirm/cancel pair, a two- or three-action lane, the action row of a modal. Reach for ButtonRail instead whenever the action count, label length, or available width could cause overflow. A group is not subnavigation: use SubNavigationPager when the controls swap peer content.
A group holds button-like children only: Button, QuickReplyButton, and ButtonDivider.

Usage

import { Button, ButtonGroup, ButtonGroupAlignment } from '@wearables-ui-toolkit/mrbd';

<ButtonGroup alignment={ButtonGroupAlignment.CENTER}>
  <Button icon={closeIcon} title="Cancel" onClick={cancel} />
  <Button icon={checkIcon} title="Confirm" onClick={confirm} />
</ButtonGroup>;
Docked at the bottom of a route, a group follows the same placement rules as a rail: outside the route’s scroll container, with the xsmall spacing token below the buttons for rubber-banding clearance.
import { Button, ButtonGroup, ButtonGroupAlignment } from '@wearables-ui-toolkit/mrbd';

<div className="page">
  <div className="page-content">{content}</div>
  <div className="action-dock">
    <ButtonGroup alignment={ButtonGroupAlignment.END}>
      <Button icon={skipIcon} title="Skip" onClick={skip} />
      <Button icon={checkIcon} title="Done" onClick={finish} />
    </ButtonGroup>
  </div>
</div>;
.page {
  display: grid;
  grid-template-rows: 1fr auto;
  height: 100%;
}

.action-dock {
  padding-bottom: var(--uit-spacing-xsmall);
}
Tracking which child holds focus is useful when a surrounding surface has to react — for example, dimming a preview while an action is focused:
<ButtonGroup
  onChildFocusChange={(group, focusedChild) => {
    setActionFocused(focusedChild != null);
  }}
>
  {actions}
</ButtonGroup>

Props

The full prop type is ButtonGroupProps.
PropTypeDefaultDescription
children
ReactNode
—
The group’s children. Required. Use button-like toolkit components only.
alignment
ButtonGroupAlignment
ButtonGroupAlignment.CENTER
Where the set of children sits within the group’s width.
onChildFocusChange
(group: HTMLElement \| null, focusedChild: HTMLElement \| null) => void
—
Called with the group element and the child that gained focus, and with a null child when focus leaves the group entirely.
className
string
''
Extra class on the group element.
style
CSSProperties
—
Extra inline styles on the group element.
ButtonGroup forwards its ref to the group’s root HTMLDivElement. It does not spread arbitrary DOM attributes: className, style, and ref are the only ways to reach the root element.

Types and enums

ButtonGroupAlignment

MemberDescription
START
Align the set to the leading edge (left in left-to-right layouts)
CENTER
Center the set within the group (default)
END
Align the set to the trailing edge (right in left-to-right layouts)

Behavior

Layout. Children are laid out in a single horizontal row, vertically centered, justified according to alignment. Overflow is deliberately left visible so a focused child’s scale and glow are not clipped — the group is not a scroller, so content wider than the available space simply spills.
Spacing. Button-like children publish a layout compensation as they collapse and expand, and the group applies it as horizontal margins so the gaps stay even through the animation. The compensation is applied to the group’s direct children, so wrapping a child in your own element, or adding a gap or margins of your own, breaks the spacing.
Height. The group measures every direct child that reports a height and sets the row’s minimum height to the tallest one. A group of quick replies is therefore not padded out to the full button height, and a mixed group still aligns correctly. When no child reports a height, the group falls back to the standard button height.
Focus navigation. Left and right move focus to the previous and next focusable descendant, and the group consumes the event when it moves focus. At the ends, and for every other key, the event is left alone, so focus can travel out of the group to a neighbouring region and vertical navigation is unaffected. Children that are not focusable are skipped; disabled buttons keep their focus stop and stay reachable, so their tooltip can still be read.
Child contract. The built-in button-like components stamp a sizing marker on their root element, which is what the group reads for the row height. In development builds, a direct child that carries that marker neither on itself nor on a descendant logs a console warning. It is a soft warning rather than an error: an unrecognized child still renders, but the group cannot size or space around it correctly. A wrapped button therefore stays silent while still losing the spacing compensation, and the group falls back to measuring the wrapper instead of reading the marker — another reason not to wrap children.

Accessibility

  • The group is a plain layout container with no role and no accessible name; children own their own semantics.
  • The group itself is not focusable and never becomes an activation target, so it adds no extra stop to directional navigation.
  • Label icon-only children yourself, with ariaLabel or aria-label on the child.
  • Because the group carries no grouping semantics, any relationship between the actions must be clear from their labels or from surrounding content.
  • Keep the set to a few actions that fit comfortably at the available width.
  • Use ButtonDivider only between genuinely distinct logical sections.
  • Dock a page-level group at the bottom of the display, outside the route’s scroll container.
  • Use onChildFocusChange when a surrounding surface needs to react to focus entering or leaving the action row.
  • Verify focus travel into, across, and out of the group.

Avoid

  • For content that may overflow, use ButtonRail instead of ButtonGroup.
  • Keep the group outside lists, and use SubNavigation when the actions serve as tabs.
  • Pass supported children directly. Custom flex or grid wrappers interfere with the group’s height measurement and spacing compensation.
  • Let the group manage its spacing rather than adding a custom gap or margins.
  • Use a dedicated container when you need scrolling, clipping, or accessible grouping semantics; ButtonGroup does not provide them.