A11y Async Button

Vanilla TypeScript accessibility plugin

A11y Async Button

Accessible async state management for semantic button elements. Use it to show loading, success, error, reset, and locked states while keeping native button behavior intact.

Zero runtime dependencies Native button semantics Framework agnostic

Quick facts

Package overview

Package
a11y-async-button
Type
Vanilla TypeScript, browser runtime
Dependencies
None at runtime
Best for
Save, send, submit, and retry actions with async feedback

Live demo

Try loading, success, and error states

The buttons below are real <button> elements. The first simulates a successful save. The second simulates an error and then resets.

idle

Success state updates the button text, removes busy semantics, announces the result, and returns to idle after the reset delay.

idle

Error state uses the same semantic button, announces the failure, and keeps repeat activation blocked while work is running.

Example library

Choose the pattern you need

Start with the core patterns, then explore focused addons for common product flows and development workflows.

Installation

Install from npm

Import the JavaScript API and the optional baseline stylesheet from the package.

npm install a11y-async-button
pnpm add a11y-async-button
yarn add a11y-async-button

Basic usage

Enhance an existing button

Markup

<button
  class="a11y-async-button"
  type="button"
  data-a11y-async-button
  data-loading-text="Saving..."
  data-success-text="Saved"
  data-error-text="Could not save"
  data-reset-delay="2000"
>
  <span class="a11y-async-button__text" data-a11y-async-button-text>
    Save changes
  </span>
</button>

JavaScript

import { createAsyncButton } from "a11y-async-button";
import "a11y-async-button/styles.css";

const button = document.querySelector("[data-a11y-async-button]");

if (button instanceof HTMLButtonElement) {
  const asyncButton = createAsyncButton(button);

  button.addEventListener("click", async () => {
    if (asyncButton.getState() !== "idle") return;

    asyncButton.loading();

    try {
      await saveChanges();
      asyncButton.success();
    } catch {
      asyncButton.error();
    }
  });
}

API

Options, methods, and events

Options can be passed to createAsyncButton() or read from matching data attributes.

API Description
createAsyncButton(element, options) Initializes one button and returns an instance.
initAsyncButtons(options, root) Initializes all [data-a11y-async-button] buttons in the given root.
loading(), success(), error() Move the button through async action states.
reset(), lock(), unlock() Restore idle state or control activation manually.
getState(), setState(), destroy() Inspect state, set a state directly, or remove plugin behavior.
createAsyncButtonForm(form, options) Optional form addon for validation handoff, async submit state, and visible status feedback.
initAsyncButtonForms(options, root) Initializes all [data-a11y-async-form] forms in the given root.
createAsyncButtonRetry(button, options) Optional retry addon for recoverable async action failures.
initAsyncButtonRetries(options, root) Initializes all [data-a11y-async-retry] buttons in the given root.
createAsyncButtonStatus(button, options) Optional status addon for visible feedback synced from async button state.
initAsyncButtonStatuses(options, root) Initializes all [data-a11y-async-status-button] buttons in the given root.
createAsyncButtonPreset(button, options) Optional presets addon for named or custom async button copy defaults.
initAsyncButtonPresets(options, root) Initializes all [data-a11y-async-preset] buttons in the given root.
getAsyncButtonPresetOptions(preset, options) Returns core async button options for a named or custom preset.
createAsyncButtonDebugReport(root, options) Optional debug addon that scans markup and returns structured findings.
logAsyncButtonDebugReport(report, options) Logs debug findings with severity-specific console methods.
Data attribute Purpose
data-loading-text, data-success-text, data-error-text Text used while work is loading, complete, or failed.
data-idle-text, data-reset-delay Idle text and milliseconds before success or error resets.
data-prevent-double-click Set to false to allow repeat activation while loading.
data-preserve-width, data-use-native-disabled Preserve initial width or use native disabled while locked.
data-announce, data-live-region Control managed or external live-region announcements.
data-a11y-async-form, data-a11y-async-form-status Opt in to the separate form addon and connect visible submit status output.
data-a11y-async-retry, data-max-attempts Opt in to the separate retry addon and configure recoverable attempt limits.
data-a11y-async-status-button, data-status-target Opt in to the separate status addon and connect an existing visible status element.
data-a11y-async-preset Opt in to the separate presets addon and choose a named action preset.

Lifecycle events bubble from the button, including a11y-async-button:init, a11y-async-button:loading, a11y-async-button:success, a11y-async-button:error, a11y-async-button:reset, a11y-async-button:state-change, a11y-async-button:lock, a11y-async-button:unlock, and a11y-async-button:destroy.

Accessibility behavior

Semantics and announcements

Keyboard interaction

  • Uses native button activation for Enter and Space.
  • Blocks repeated activation while loading or locked.
  • Does not trap focus or move focus after state changes.

Screen reader behavior

  • Sets aria-busy during async work.
  • Uses aria-disabled by default so focus does not disappear.
  • Announces loading, success, and error text through a managed or external live region.

Styling hooks

CSS classes and custom properties

The default stylesheet uses the a11y-async-button BEM block with state classes and public custom properties.

Limitations

What the plugin expects