Foundations
Accessibility
StableAccessibility work that happens at review time is always late. In Foundry it lives in the primitive.
Updated in v1.0.0
No certification is claimed
The contracts
These hold across every component in the library, without exception.
- One focus treatment. Focus is drawn by a single
:focus-visiblerule in the token file. No component draws its own, so focus is identical everywhere and never appears for pointer users. - Focus goes in and comes back. Every overlay traps focus while open and returns it to the trigger on close. Trapping without restoring strands keyboard users at the top of the document.
- Status is never colour alone. Every status carries an icon and a word. Every delta carries an arrow and a sign as well as a colour.
- Live regions are polite by default. Only genuine errors interrupt. Form errors are announced once as a count, not per field on every keystroke.
- Errors move focus. A failed submit moves focus to the first invalid control, so a keyboard user is taken to the problem rather than told one exists.
- Reduced motion is honoured globally. Every animation in the library is disabled under
prefers-reduced-motion, including the marquee and the spinner.
Keyboard reference
- Component
- Dialog / Drawer
- Keys
- Tab, Shift+Tab, Escape
- Behaviour
- Focus trapped inside, Escape closes, focus returns to the trigger.
- Component
- Dropdown menu
- Keys
- ↑ ↓ Home End Enter Escape Tab
- Behaviour
- Roving focus between items; ArrowUp opens on the last item.
- Component
- Tabs
- Keys
- ← → Home End Enter Space
- Behaviour
- Manual activation — arrows move focus, Enter or Space selects.
- Component
- Combobox
- Keys
- ↑ ↓ Home End Enter Escape
- Behaviour
- Active descendant moves; DOM focus stays in the input.
- Component
- Command menu
- Keys
- ⌘K, ↑ ↓ Home End Enter Escape
- Behaviour
- Modal combobox; focus stays in the input for the whole interaction.
- Component
- Slider
- Keys
- ← → ↑ ↓ Home End PgUp PgDn
- Behaviour
- Native range semantics with a formatted aria-valuetext.
- Component
- OTP field
- Keys
- ← → Backspace, paste
- Behaviour
- Auto-advance, step-back on empty, full-code paste fills every box.
- Component
- Tooltip
- Keys
- Focus, Escape
- Behaviour
- Opens on keyboard focus as well as hover; Escape dismisses.
| Component | Keys | Behaviour |
|---|---|---|
| Dialog / Drawer | Tab, Shift+Tab, Escape | Focus trapped inside, Escape closes, focus returns to the trigger. |
| Dropdown menu | ↑ ↓ Home End Enter Escape Tab | Roving focus between items; ArrowUp opens on the last item. |
| Tabs | ← → Home End Enter Space | Manual activation — arrows move focus, Enter or Space selects. |
| Combobox | ↑ ↓ Home End Enter Escape | Active descendant moves; DOM focus stays in the input. |
| Command menu | ⌘K, ↑ ↓ Home End Enter Escape | Modal combobox; focus stays in the input for the whole interaction. |
| Slider | ← → ↑ ↓ Home End PgUp PgDn | Native range semantics with a formatted aria-valuetext. |
| OTP field | ← → Backspace, paste | Auto-advance, step-back on empty, full-code paste fills every box. |
| Tooltip | Focus, Escape | Opens on keyboard focus as well as hover; Escape dismisses. |
Decisions worth explaining
Manual tab activation
Tabs use manual activation: arrow keys move focus, Enter or Space activates. Automatic activation feels responsive with a mouse and quietly punishes keyboard users, who mount every panel they arrow past.
Ids derived from names, not useId
Form field ids come from the field’s name rather than useId. That keeps Field renderable from a Server Component, and makes the markup byte-identical between server and client — so a form is fully labelled before any JavaScript runs.
Native controls wherever they work
Select, date field, checkbox, radio, switch and slider all keep a real native control in the DOM. Native brings keyboard support, mobile pickers and assistive-tech semantics for free; only the appearance is custom.
Disclosures over menus for navigation
The mega menu is a disclosure, not role="menu". Its contents are links and headings, so normal Tab order is both simpler and more usable than forcing roving menuitem focus across a page of navigation.
How to verify it yourself
- Tab through a page without touching the mouse. Every interactive element should show a ring, in order.
- Open a dialog, press Escape, and check that focus is back on the button that opened it.
- Submit an empty form and confirm focus lands on the first invalid field.
- Turn on reduced motion at the OS level and reload — nothing should animate.
- Set the display to greyscale and check that every status is still readable.
What is not covered
- No third-party audit or certification.
- Manual screen-reader passes are partial. Automated checks cover structure and naming; they cannot judge whether an announcement is useful.
- No right-to-left layout support yet.
- Colour contrast is designed to pass at the sizes used here, but a palette you author yourself needs its own check.
Every component page carries its own accessibility notes — see Dialog for the most involved example.