v1.0.0

Accessibility

BMX targets WCAG 2.2 Level AA. This page records what that means concretely, which is more useful to a procurement questionnaire than the claim on its own.

The load-bearing decisions

Real elements, not ARIA impersonations. bmx-button renders a <button> or an <a>. The role, the keyboard behaviour, the form participation and the high-contrast rendering all come from the platform, which gets them right on browsers and assistive technology we have never tested on. A <div role="button"> with a keydown handler is where component libraries accumulate their accessibility bugs.

Form participation via ElementInternals. A submit button inside a shadow root submits nothing unless the element is form-associated. BMX uses ElementInternals and calls requestSubmit(), so constraint validation runs and the submit event stays cancellable. Libraries that call submit() instead silently skip validation — a form with a required field submits empty.

One focus indicator, everywhere. A single --bmx-focus-ring-* token set drives every component, so keyboard focus looks identical across the library. The ring is drawn as two stacked shadows — an offset in the surface colour, then the ring itself — which keeps it visible against both a light and a dark control of the same tone. It appears on :focus-visible only, so a mouse click does not leave one behind. Width and offset satisfy SC 2.4.11 (Focus Appearance) at every size step, and thicken further under prefers-contrast: more.

Composite widgets take one Tab stop. bmx-button-group implements the WAI-ARIA roving-tabindex pattern: the group is one stop, arrow keys move within it, Home and End jump to the ends, and disabled items are skipped rather than focused and ignored. Eight individually tabbable segments would otherwise cost a keyboard user eight presses to get past.

The focusable element lives inside each child's shadow root, where a parent cannot reach it — which is why bmx-button exposes rovingTabindex. Without it, a composite widget cannot take its children out of the tab order, and cannot implement the pattern at all.

Right-to-left is arithmetic, not a stylesheet. Arrow-key direction is computed from the resolved writing direction, so ArrowRight moves towards the start in Arabic and Hebrew. Layout uses logical properties (inline-size, padding-inline, inset-inline-end) throughout.

Media queries the library honours

Query What changes
prefers-reduced-motion: reduce Every duration token drops to 0ms. The busy spinner stops rotating and pulses instead — an indicator that does not move at all reads as a frozen interface. (SC 2.3.3)
prefers-contrast: more Borders and the focus ring thicken; disabled opacity rises. (SC 1.4.11)
forced-colors: active Shadows are dropped, the focus ring becomes Highlight, and state gains a structural signal so it survives Windows High Contrast, where every colour is replaced.
pointer: coarse Hit targets grow to 44px without the control growing, so a dense desktop toolbar stays dense. (SC 2.5.8)

Target size

Every size step meets SC 2.5.8's 24×24 CSS pixel minimum, including xs, whose control is 26px tall but whose hit area is grown by a pseudo-element rather than by padding. The control stays small; the target does not.

Colour is never the only signal

A pressed toggle changes weight as well as colour, and carries an inset ring. A confirming button changes its label, not just its border. Both survive greyscale, colour-vision deficiency and forced-colors. (SC 1.4.1)

Names

An icon-only button with no accessible name is unusable to a screen-reader user and looks perfectly fine to everybody else, which is why that bug survives to production. BMX warns in the console, naming the offending element, whenever a button renders icon-only without label, aria-label or aria-labelledby.

Disabled controls

disabled removes the control from the tab order, which is the right default but means a keyboard user never learns the control exists — let alone why it is unavailable. focusableWhenDisabled applies WCAG 2.2's preferred alternative: aria-disabled, a kept Tab stop, and an explanation reachable through describedBy.

Announcements

Each component owns one polite live region, rendered from the start and updated rarely. A live region added to the DOM at the moment it gains text is frequently missed entirely by screen readers, which need it present beforehand.

What is not claimed

  • Level AAA is not targeted.
  • Conformance of your application is yours: BMX gives you conformant components, and an application can still misuse them — an unlabelled group, a colour override that fails contrast, a dialog opened without moving focus.
  • Testing to date is Chromium, Firefox and WebKit with keyboard traversal, plus automated checks. Screen-reader verification with NVDA, JAWS and VoiceOver is scheduled per component as the library grows.