Skip to content

Button

Buttons and ActionsSince 0.1.0
import { Button } from '@absoluteui/components/button';
import '@absoluteui/components/button/style.css';

Using Angular? See Button for Angular.

Triggers an action. Button enhances a native <button>; semantics, form submission, and keyboard activation stay native. Since 0.9.0 it also enhances a link (<a href>) that should look like a button; the element stays a link.

  • Submitting forms or triggering in-page actions.
  • Icon-only actions that have an accessible name.
  • Actions with a busy state while an operation is pending.
  • Navigation to another page or URL: use a link (<a href>).
  • Toggling a persistent on/off state: use Switch.
  • Selecting one option among several: use a segmented control or radio group.
import { Button } from '@absoluteui/components/button';
const el = document.querySelector<HTMLButtonElement>('#save');
if (el) {
const save = Button.enhance(el, { variant: 'primary' });
el.addEventListener('click', () => {
save.setLoading(true);
});
}
import { Button } from '@absoluteui/components/button';
const search = Button.create({ icon: 'search', label: 'Search', variant: 'ghost' });
document.body.append(search.element);
import { configureAbsoluteUI } from '@absoluteui/core';
import { Button } from '@absoluteui/components/button';
configureAbsoluteUI({ common: { density: 'compact' }, components: { button: { size: 'sm' } } });
const b = Button.create({ text: 'Apply' });
document.body.append(b.element);
import { Button } from '@absoluteui/components/button';
import '@absoluteui/components/button/style.css';
const b = Button.create({ text: 'Save', variant: 'primary', type: 'submit' });
document.querySelector('form')?.append(b.element);
import { Button } from '@absoluteui/components/button';
const el = document.querySelector<HTMLButtonElement>('#save');
if (el) {
const save = Button.enhance(el, { variant: 'primary' });
el.addEventListener('click', () => {
save.setLoading(true);
});
}
<button class="aui-button aui-button--{variant} aui-button--{size}">
[svg.aui-icon.aui-button__icon] (optional, start or end)
label text
[span visually hidden ", Loading"] (loading only)
</button>
Button.enhance<E extends HTMLButtonElement | HTMLAnchorElement>(element: E, options?: ButtonOptions): Button<E>; Button.create(options?: ButtonOptions & { text?: string; type?: 'button' | 'submit' | 'reset' }, doc?: Document): Button<HTMLButtonElement>

Global configuration key: button (see Configuration).

Option Type Default Description
variant 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' "secondary" Visual emphasis. Globally configurable.
size 'sm' | 'md' | 'lg' "md" Control height and font size. Globally configurable.
loading boolean false Busy state: aria-disabled, activation blocked, spinner shown, ‘Loading’ added to the accessible name.
icon IconRef | null null Icon name or definition.
iconPosition 'start' | 'end' "start" Icon placement (logical).
fullWidth boolean "false" Stretch to the full inline size of the container.
disabled boolean Disabled state (since 0.9.0). Links: aria-disabled, removed from the tab order, activation blocked, href kept. Buttons: sets the native disabled property; unset leaves it alone.
label string Accessible name; required for icon-only buttons.
icons Partial<{ loading: IconRef }> Icon slot overrides.
pressed boolean | null Toggle button: aria-pressed, flips on activation; null or unset is an ordinary button (buttons only).
Property Type Description
element E (HTMLButtonElement | HTMLAnchorElement) The enhanced native button or link.
options Readonly<ButtonOptions> Current instance options.
Method Description
update(options: Partial<ButtonOptions>): void Merge and apply options.
setLoading(loading: boolean): void Toggle the busy state.
destroy(): void Remove enhancements, listeners, and subscriptions.
Event Detail Bubbles Cancelable Description
aui-change ButtonChangeDetail Yes No A toggle button was activated by the user: { pressed }.
Area Description
label Existing child text/elements of the native button.

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

Class Purpose
aui-button Base class; usable without JavaScript.
aui-button--primary | --secondary | --outline | --ghost | --danger Variant modifiers.
aui-button--sm | --md | --lg Size modifiers.
aui-button--icon-only Square icon-only layout.
aui-button--pressed Toggle button in the pressed state.
aui-button__icon Rendered icon.
aui-button__spinner Loading spinner; animation removed under reduced motion.
Slot Default icon
loading spinner

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

--aui-border-width | --aui-color-active | --aui-color-border | --aui-color-border-strong | --aui-color-danger-fill | --aui-color-danger-fill-hover | --aui-color-disabled-bg | --aui-color-focus-ring | --aui-color-hover | --aui-color-on-danger | --aui-color-on-primary | --aui-color-primary | --aui-color-primary-active | --aui-color-primary-hover | --aui-color-selected | --aui-color-selected-indicator | --aui-color-selected-text | --aui-color-surface | --aui-color-text | --aui-color-text-disabled | --aui-control-height-lg | --aui-control-height-md | --aui-control-height-sm | --aui-control-padding-x | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-size-xs | --aui-font-weight-medium | --aui-icon-size-md | --aui-icon-size-sm | --aui-line-height-tight | --aui-radius-md | --aui-space-2 | --aui-space-3 | --aui-space-5 | --aui-touch-target-min

type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
type ButtonSize = 'sm' | 'md' | 'lg';
interface ButtonOptions {
variant?: ButtonVariant;
size?: ButtonSize;
loading?: boolean;
icon?: IconRef | null;
iconPosition?: 'start' | 'end';
fullWidth?: boolean;
disabled?: boolean;
label?: string;
icons?: Partial<{ loading: IconRef }>;
}

Pattern: WAI-ARIA APG Button pattern, implemented with a native <button>.

Key Context Action
Enter Button focused Activates (native). Blocked while loading.
Space Button focused Activates on key release (native). Blocked while loading.
Tab / Shift+Tab Any Moves focus to/from the button; loading buttons stay in the tab order.
Element Role
host <button> button (native)
icon <svg> none (aria-hidden=true)
Attribute Element Value
aria-disabled host “true” while loading (button remains focusable)
aria-label host Set from label; includes ‘, Loading’ while loading

Native focus. Visible :focus-visible outline using color-focus-ring (>= 3:1 against surfaces).

Criterion Level Note
1.4.3 Contrast (Minimum) AA
1.4.11 Non-text Contrast AA
2.1.1 Keyboard A
2.4.7 Focus Visible AA
2.5.3 Label in Name A
2.5.8 Target Size (Minimum) AA
4.1.2 Name, Role, Value A

Automated test results: Accessibility and compliance.

  • The loading state is conveyed through visually hidden text in the accessible name rather than aria-busy, which buttons do not reliably announce.
  • Disabled (native) buttons are removed from the tab order by the browser; loading buttons are not.
  • Screen-reader announcement of the name change when loading starts while focused has not yet been manually verified with NVDA, JAWS, and VoiceOver.

default, hover, active, focus-visible, disabled (native disabled; disabled option on links), loading, pressed (toggle buttons: aui-button--pressed).

From Trigger To
default setLoading(true) / update({ loading: true }) loading (activation blocked, stays focusable)
loading setLoading(false) default
any native disabled = true disabled (removed from tab order by the browser)
any (link) update({ disabled: true }) disabled (aria-disabled, tabindex="-1", activation blocked; the previous tabindex returns on enable)
  • Loading is conveyed through the accessible name because aria-busy is not reliably announced on buttons.
  • Icon-only buttons without an accessible name are rejected at construction (BUTTON_ICON_ONLY_LABEL).
  • Minimum target size is 1.5rem (WCAG 2.5.8); compact sm buttons are 1.75rem high.
  • Manual screen-reader verification is pending (see Known limitations).

Inline-flex; the label does not wrap. Density tokens (compact, comfortable, spacious) change control height and padding.

Logical properties throughout; iconPosition: 'start' places the icon at the inline start in both directions.

Colors come from role tokens only. Verified in light, dark, and high-contrast themes (axe, WCAG 2.2 AA). In forced-colors mode, borders use ButtonText, disabled uses GrayText, and focus uses Highlight.

configureAbsoluteUI({ components: { button: { variant: 'primary', size: 'sm' } } });

Global changes apply to existing instances immediately.

Instance options override global and theme defaults. All options are dynamic.

icon accepts a name resolved through the global icon provider/registry or an inline IconDefinition. The icons.loading slot defaults to spinner.

Button.create sets text with textContent, so HTML strings are never parsed. No inline styles, eval, or injected <style>; verified under a strict CSP with Trusted Types enforced.

No persistence, network access, or telemetry.

One capture-phase click listener and one configuration subscription per instance. Budget: under 0.5 ms average per Button.enhance across 1,000 instances in headless Chromium (performance test).

1,000 buttons per page in the performance test.

  • Enhancing a button whose only child is an icon requires label, aria-label, or aria-labelledby.
  • A type="submit" button does not submit its form while loading.
  • A link host without href is not a link (no role, not focusable); give it an href.
  • A disabled or loading link blocks click handlers registered after enhancement, including router link handlers on the same element; modified clicks are blocked too.
  • Calling update({ variant }) with an invalid value throws, and the previous rendering stays in place.
Code Category Cause
BUTTON_HOST configuration Host is neither a <button> nor an <a>.
BUTTON_ICON_ONLY_LABEL configuration Icon-only button without an accessible name.
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION configuration Invalid or unknown option; the message names the property and expected type, never the value.

A failed construction leaves no listeners or subscriptions behind.

  • Capture-phase click listeners that a consumer registers on the same element before enhancement still run while loading.
  • Screen-reader behavior of the loading name change has not yet been manually verified with NVDA, JAWS, and VoiceOver.
  • Hosts other than <button> and <a> (for example a div with role="button").
  • HTML strings as labels.