Skip to content

Button Group

ActionsSince 0.2.0
import { ButtonGroup } from '@absoluteui/components/button-group';
import '@absoluteui/components/button-group/style.css';

Using Angular? See Button Group for Angular.

A labelled role="group" of related native buttons, visually attached (shared borders, no inner radii) or spaced, horizontal or vertical. ButtonGroup.create renders Buttons from data; ButtonGroup.enhance groups buttons that already exist in the markup. Keyboard interaction stays native: Tab moves through every button. It is not a toolbar; a Toolbar component with arrow-key navigation is planned.

  • A small set of related actions that belong together, such as Previous / Next or Copy / Paste.
  • Grouping buttons so assistive technologies announce their shared purpose.
  • Choosing one or several options: use Radio Group, Checkbox Group, or a segmented control.
  • Large sets of commands with arrow-key navigation: use Toolbar (planned).
  • Navigation between pages or views: use links or Tabs.
import { ButtonGroup, type ButtonGroupActionDetail } from '@absoluteui/components/button-group';
const host = document.createElement('div');
document.body.append(host);
ButtonGroup.create(host, {
label: 'Pagination',
size: 'sm',
buttons: [
{ icon: 'chevron-left', label: 'Previous page', value: 'prev' },
{ icon: 'chevron-right', label: 'Next page', value: 'next' },
],
});
host.addEventListener('aui-action', (event) => {
const { value } = (event as CustomEvent<ButtonGroupActionDetail>).detail;
host.dataset.last = value;
});

Enhance existing native buttons, spaced and wrapping

Section titled “Enhance existing native buttons, spaced and wrapping”
import { ButtonGroup } from '@absoluteui/components/button-group';
const element = document.createElement('div');
for (const text of ['Copy', 'Paste', 'Duplicate']) {
const button = document.createElement('button');
button.type = 'button';
button.textContent = text;
element.append(button);
}
document.body.append(element);
ButtonGroup.enhance(element, { label: 'Clipboard', attached: false });
import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { buttonGroup: { attached: false } } });
import { ButtonGroup } from '@absoluteui/components/button-group';
import '@absoluteui/components/button/style.css';
import '@absoluteui/components/button-group/style.css';
const element = document.querySelector<HTMLElement>('#clipboard-actions');
if (element) {
ButtonGroup.enhance(element, { label: 'Clipboard', attached: false });
element.addEventListener('click', (event) => {
if (event.target instanceof HTMLButtonElement) element.dataset.last = event.target.textContent;
});
}
import { ButtonGroup } from '@absoluteui/components/button-group';
const host = document.createElement('div');
document.body.append(host);
ButtonGroup.create(host, {
label: 'Pagination',
buttons: [
{ icon: 'chevron-left', label: 'Previous page', value: 'prev' },
{ icon: 'chevron-right', label: 'Next page', value: 'next' },
],
});
<div class="aui-button-group aui-button-group--horizontal aui-button-group--attached" role="group" aria-label="Label">
<button class="aui-button aui-button--secondary aui-button--md">First</button>
<button class="aui-button ...">Second</button>
<button class="aui-button aui-button--icon-only" aria-label="More"><svg class="aui-icon" aria-hidden="true"></button>
</div>
ButtonGroup.create(host: HTMLElement, options: ButtonGroupOptions): ButtonGroup; ButtonGroup.enhance(element: HTMLElement, options: ButtonGroupOptions): ButtonGroup

Global configuration key: buttonGroup (see Configuration).

Option Type Default Description
label string Accessible name of the group (aria-label). Required.
size 'sm' | 'md' | 'lg' Size of the Buttons the group creates or enhances; defaults to the Button configuration.
buttons readonly { text?: string; icon?: IconRef; label?: string; variant?: ButtonVariant; value?: string; disabled?: boolean }[] create() only: Buttons to render. Icon-only buttons need a label.
orientation 'horizontal' | 'vertical' "horizontal" Layout direction. Globally configurable.
attached boolean true Shared borders and no inner radii; false spaces the buttons with a gap and lets horizontal groups wrap. Globally configurable.
Property Type Description
element HTMLElement The group element (role=group).
buttons HTMLButtonElement[] Direct child buttons, in DOM order.
options Readonly<ButtonGroupOptions> Current instance options.
Method Description
ButtonGroup.enhance(element: HTMLElement, options: ButtonGroupOptions): ButtonGroup Enhance an existing element containing native buttons; plain buttons become Buttons until destroy.
update(options: Partial<ButtonGroupOptions>): void Merge and apply options; a new buttons array rebuilds the Buttons.
destroy(): void Remove listeners and subscriptions, destroy created or enhanced Buttons, restore the element’s role and aria-label.
Event Detail Bubbles Cancelable Description
aui-action { value: string } Yes No create() only: a button was activated. value defaults to the button index.
Area Description
buttons Native buttons (created from buttons, or the existing children with enhance()).

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-button-group Group element.
aui-button-group--horizontal | --vertical Orientation modifiers.
aui-button-group--attached Shared borders, removed inner radii; horizontal groups scroll instead of wrapping.

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-border-width | --aui-color-focus-ring | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-space-2

type ButtonGroupOrientation = 'horizontal' | 'vertical';
interface ButtonGroupButton { text?: string; icon?: IconRef; label?: string; variant?: ButtonVariant; value?: string; disabled?: boolean }
interface ButtonGroupOptions { label: string; orientation?: ButtonGroupOrientation; attached?: boolean; size?: ButtonSize; buttons?: readonly ButtonGroupButton[] }
interface ButtonGroupActionDetail { value: string }

Pattern: Native buttons inside an element with role=group and an accessible name (WAI-ARIA group role). Not the APG Toolbar pattern.

Key Context Action
Tab / Shift+Tab Any Moves through every enabled button in DOM order (native).
Enter / Space Button focused Activates the button (native).
Element Role
group element group
buttons button (native)
icons none (aria-hidden=true)
Attribute Element Value
aria-label group element The label option
aria-label icon-only button The button’s label

Each button shows its own :focus-visible outline. In attached groups the focused button is raised and the ring is inset so the scroll container does not clip it.

Criterion Level Note
1.3.1 Info and Relationships A
1.4.11 Non-text Contrast AA
2.1.1 Keyboard A
2.4.7 Focus Visible AA
4.1.2 Name, Role, Value A

Automated test results: Accessibility and compliance.

  • aria-orientation is not set: the group role does not support it and there is no arrow-key navigation.
  • For a toolbar with arrow-key roving focus, a Toolbar component is planned.
  • Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.

horizontal, vertical, attached, spaced; per button: default, hover, focus-visible, disabled.

From Trigger To
any update({ orientation }) or global config new layout
any update({ buttons }) Buttons rebuilt
any update({ size }) Buttons resized in place
  • Screen readers announce the group label when focus enters the group.
  • Disabled buttons use native disabled and leave the tab order.
  • In attached groups the hovered or focused button is raised so its full border and focus ring are visible; the ring is inset so the scroll container cannot clip it.
  • Manual screen-reader verification is pending (see Known limitations).

Spaced horizontal groups (attached: false) wrap at narrow widths. Attached horizontal groups never wrap, because wrapping would break the shared borders; they are limited to the container width and scroll horizontally. Use attached: false for groups that must stay fully visible on small screens. Vertical groups stack.

Logical properties throughout: the first button keeps the start radii and the last keeps the end radii in both directions.

Buttons use their own role tokens. In forced-colors mode focus uses a Highlight outline and button borders use ButtonText.

configureAbsoluteUI({ components: { buttonGroup: { attached: false } } });

Changes apply to existing instances immediately.

Instance options override global and theme defaults. All options are dynamic. size falls back to the Button configuration.

None on the group. Each entry in buttons takes an icon.

Button text and labels use textContent. CSP-safe: no inline styles or handlers.

No persistence, network access, or telemetry.

One delegated click listener (create only) and one configuration subscription, plus one Button per button. Budget: under 2 ms for 10 buttons (not yet covered by the performance test).

Intended for small groups of 2 to 7 buttons. Unit-tested with up to three.

  • An icon-only entry without label throws BUTTON_ICON_ONLY_LABEL; Buttons already created for that call are discarded.
  • enhance() only considers direct child buttons; nested buttons are not styled as part of the group.
  • Consumer-enhanced Buttons inside an enhanced group keep their own size and variant.
  • Disabled buttons emit neither click nor aui-action.
Code Category Cause
BUTTON_GROUP_OPTIONS configuration Missing label, invalid size, or buttons passed to enhance().
BUTTON_ICON_ONLY_LABEL configuration An icon-only button has no label.
CFG_INVALID_VALUE configuration Invalid orientation or attached value.

A failed construction leaves no rendering, listeners, or subscriptions behind.

  • No arrow-key navigation; a Toolbar component is planned.
  • Attached horizontal groups scroll rather than wrap at narrow widths.
  • No pressed/toggle state management.
  • Screen-reader behavior has not yet been manually verified with NVDA, JAWS, and VoiceOver.
  • Split buttons and menu buttons.
  • Managing aria-pressed toggle state (set it on the buttons yourself).