The keyboard map
One row per pattern: what the platform gives you for free, and what the opt-in module adds. The full table with markup is in the docs.
| Pattern | Native keyboard | Opt-in JS adds |
|---|---|---|
| Popover menu | Tab reaches the trigger; Enter opens it, autofocus moves focus in, Tab walks the items, Esc closes and returns focus | js/popover-menu.js — arrows + Home/End, and close on Tab |
| Tabs | Tab through every tab; Enter/click switches | js/tabs.js — roving tabindex, arrows, Home/End |
| Sortable table | Tab through the header buttons; Enter sorts | js/table-sort.js — arrows move between the sort buttons |
| Filter input | Plain text editing | js/filter-clear.js — Escape clears and reports bf:filterclear |
| Dialog (nested too) | Tab trap, Esc closes, focus returns to the opener | Nothing — showModal() is one native line |
| Accordion | Enter/Space toggles, arrow keys natively | Nothing |
Popover menu
Open it — the first item carries autofocus, so focus moves in on open — then press ↓/↑ (and Home/End) to move between items, Esc or Tab to close; focus lands back on the trigger. Without js/popover-menu.js the same menu keeps a keyboard floor (Tab walks the items, Esc returns focus) but no arrows; Verify's roving-focus rule warns only when a menu has neither the module nor autofocus.
Tabs
One Tab stop for the whole list; arrows move it and switch the panel, Home/End jump to the ends. Without the module every tab is a Tab stop and all panels show — the no-JS floor.
Production is locked behind a manual approval.
Staging rebuilds on every push to main.
Preview environments expire after 48 hours.
Sortable table
Focus a header button, then press →/← to move between the sort controls — the same roving math the tablist uses, clamped at the ends. Enter/Space sorts (a real button, so it always did).
| api | 12 |
| web | 3 |
| worker | 5 |
Filter — Escape clears
Type to filter the list; press Esc to clear the input — the module reports bf:filterclear so a page's filter logic re-runs. No native primitive clears an input on Escape; the module is that one shared line.
6 of 6 shown
- CSS
- JS
- HTML
- A11y
- Tokens
- Themes
Nested dialogs
A dialog opened from inside a dialog is still one topmost layer: Esc closes the inner one first, Tab stays inside the top dialog, and focus returns to the button that opened it — all native, no module. The line below tracks focus live.
focus: —
User preferences
The opt-in components/a11y-prefs.css layer (imported on this page) answers three preferences the core palette doesn't assume — set one in your OS and reload:
prefers-contrast: more
The core swaps the palette to black-on-white; the layer doubles the shared border width and flattens the alpha tints. Not forced-colors — that one already gets shape, not color.
prefers-reduced-transparency: reduce
Every alpha flattens: the dialog backdrop goes solid, subtle tints mix into the surface, and the skeleton stops sweeping.
prefers-reduced-data: reduce
Decorative repaint goes — the shimmer stops. The real payload (images, fonts, JS) is yours to budget; the docs say so.
A skeleton, so the preference has something to act on:
Event log
Listener
document.addEventListener(
"bf:filterclear",
(e) => refilter(e.target)
);