Skip to content

Barefoot CSS v7.8

Keyboard & A11y, beyond the component

Every surface below is a native element with its keyboard contract intact — and where the platform stops short (arrow-key roving, sort headers, filter Escape, focus into a popover menu), one small opt-in module finishes the sentence. Nothing here is a div with an onclick.

Back to the conformance demo · playground · keyboard docs · a11y docs.

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 keyboard contracts — native vs. the opt-in module
Pattern Native keyboard Opt-in JS adds
Popover menuTab reaches the trigger; Enter opens it, autofocus moves focus in, Tab walks the items, Esc closes and returns focusjs/popover-menu.js — arrows + Home/End, and close on Tab
TabsTab through every tab; Enter/click switchesjs/tabs.js — roving tabindex, arrows, Home/End
Sortable tableTab through the header buttons; Enter sortsjs/table-sort.js — arrows move between the sort buttons
Filter inputPlain text editingjs/filter-clear.js — Escape clears and reports bf:filterclear
Dialog (nested too)Tab trap, Esc closes, focus returns to the openerNothing — showModal() is one native line
AccordionEnter/Space toggles, arrow keys nativelyNothing

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.

Jump to the map

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).

Arrow keys rove the header row; the sort reports bf:sort.
api12
web3
worker5

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: —

Confirm deployment

Deploys to production need a policy acknowledgment.

Deployment policy

Deploys are atomic and reversible for 30 minutes.

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) );