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.