ARIA Authoring Practices, implemented
The components that are
actually hard to get right.
Not buttons and cards. A focus trap that cannot be escaped, a combobox that keeps DOM focus in the input, a live region that actually announces. Eleven patterns, zero dependencies, no build step — plain ES modules, and about a third of the source is comments explaining why each decision is what it is.
-
Modal dialog
Focus trap, focus restoration,
Open demo →inertbackground, Escape to dismiss. -
Combobox
ARIA 1.2,
Open demo →aria-activedescendant, debounced result count. -
Tabs
Roving tabindex, arrow navigation, automatic and manual activation.
Open demo → -
Disclosure & accordion
Open demo →aria-expanded, real heading structure, arrow navigation. -
Sortable table
Open demo →aria-sorton one header,scope, caption, announced order. -
Listbox
Single and multi-select, type-ahead, selection independent of focus.
Open demo → -
Menu button
Roving focus, cycling type-ahead, checkbox and radio items.
Open demo → -
Slider
Open demo →aria-valuetext, and range bounds that move with the other thumb. -
Tooltip
WCAG 1.4.13: dismissible, hoverable, persistent.
Open demo → -
Toasts
Politeness levels, pausable timers, focus that never disappears.
Open demo →
What this is trying to prove
Most component libraries that advertise accessibility have a focus trap
built on a single keydown handler for Tab. That
handler is escapable in four ways: a click on a background control, a
script calling .focus(), focus returning from the browser's
own chrome, and a screen reader's virtual cursor, which does not use
Tab at all.
The implementation here uses three mechanisms — inert on
the background, a wrapping Tab handler, and a
focusin backstop — because each one has a hole the other
two cover. The reasoning for every such decision is written into the
source, next to the code it explains.
Verification
187 automated assertions run in a real browser — real focus order, real
inert, real layout — because jsdom implements none of
those. Announcements are a separate matter: no automated tool can hear
VoiceOver, so each component ships a manual test script in the README.