Docs Architecture
Accessibility policy
Accessibility is a runtime contract, not a demo-level concern. Every interactive behavior defines its semantics, keyboard, focus and ARIA in a written contract, is tested in a real browser, and is never traded for bytes.
On this page
Policy
- Native semantics first. Where a native element provides the widget model, the primitive uses it and adds no role. Where it does not (tabs, menu), the contract follows the WAI-ARIA Authoring Practices for that pattern.
- Correctness before bytes. A change that saves bytes and breaks keyboard navigation, screen reader semantics, focus restoration, disabled behavior, RTL, IME composition or native text editing is invalid. The priority order is correctness, accessibility, developer experience, performance, then bundle size.
- Tests check behavior, not only attributes: that ArrowRight moves focus, that Home focuses the first tab, that manual activation does not switch the panel, that a disabled trigger is skipped, that nested content does not break routing.
- Authored ids. Every ARIA relationship uses ids the author wrote, so server-rendered markup is correct before hydration and the same on both sides.
- Automated checks are necessary and insufficient. axe and keyboard tests are not screen-reader testing, and the site says which of the two it has.
The contract
Every primitive ships a contract.md. The sections below are read from the tabs
contract when this site is built; every contract has the same structure, so a missing section is
visible as a gap in the file, not as a claim on this page.
- Required semantics
- Required parts
- Options
- Keyboard interaction
- Focus rules
- Disabled behavior
- Orientation
- RTL
- ARIA relationships
- Events
- Platform features and Baseline
- SSR expectations
- JS-disabled behavior
- Runtime budget
- Test cases
The contracts: accordion, alert, alert-dialog, autocomplete, avatar, badge, breadcrumb, button, button-group, calendar, card, carousel, checkbox, color-field, combobox, context-menu, date-field, date-picker, descriptions, dialog, disclosure, drawer, empty, field, file-drop, file-field, hover-card, input, kbd, listbox, menu, menubar, meter, number-field, otp-field, pagination, popover, progress, radio-group, range-slider, rating, scroll-area, segmented, select, separator, skeleton, slider, spinner, splitter, steps, switch, tabs, tag, timeline, toast, toggle, toggle-group, toolbar, tooltip, tree. Native primitives use the same file to document the platform features they rely on, their Baseline status and the fallback (Browser support).
Focus and portal policy
There is no universal focus manager; each primitive has a specific focus contract. Dialog uses
the native modal focus behavior: showModal() moves focus in, Tab stays inside
because the rest of the document is inert, closing returns focus to the invoker. Tabs use a
roving tabindex. Menu focuses the first enabled item on open and returns focus to
the invoker on close. The combobox keeps DOM focus in the input and exposes the active option
through aria-activedescendant.
There is no portal primitive, no overlay manager and no z-index context: modal dialogs and popovers participate in the browser's top layer. Positioning follows one priority: the implicit anchor relationship of the invoker, then CSS anchor positioning, then the CSS fallback placement. No JavaScript positioning engine is imported by default.
Regression budget
Bundle size is not the only regression the repository blocks. For every interactive primitive the contract's test cases are the checklist; a pull request that changes a behavior runs them in a real browser, and the size budgets are checked in the same gate. A byte saved is never an argument against a failing keyboard test.
Test layers
| Layer | What it proves | Where |
|---|---|---|
| Contract tests | Tier, routed events, registration module and native base of every primitive | packages/primitives/tests/registry.test.ts |
| Algorithm tests | Next and previous enabled item, Home and End, RTL, typeahead matching | packages/core/tests, packages/primitives/tests/tabs.test.ts, menu.test.ts |
| Browser interaction tests | Focus, dialog, popover, top layer and anchor positioning in a real browser | apps/playground/tests/primitives/native.spec.ts, tabs.spec.ts, menu.spec.ts |
| Keyboard contract tests | Arrow keys, Home and End, activation modes, disabled skipping, Escape and focus return | apps/playground/tests/primitives/tabs.spec.ts, menu.spec.ts, table/table.spec.ts |
| JavaScript-disabled tests | The native contracts with page JavaScript off | apps/playground/tests/primitives/native.spec.ts |
| SSR tests | Server HTML with authored state and ids; hydration changes nothing | apps/playground/tests/ssr.spec.ts, packages/*/tests/ssr.test.ts |
| Automated accessibility scans | No serious or critical axe violation on every page and on open dialog, popover and menu states | apps/playground/tests/primitives/a11y.spec.ts, apps/docs/tests/routes.spec.ts |
| Production bundle tests | The shipping invariants against real production output | fixtures/*, apps/playground/tests/bundle.spec.ts |
Browser tests run with Playwright, never with a DOM simulation, for anything that involves focus, the top layer or anchor positioning; the unit tests under happy-dom cover the algorithmic part and shim the Popover API where a test needs it.
Test matrix
The status per area and browser as recorded in the claim registry and the audit of 2026-09-27. "Tested in Chromium" means Google Chrome locally and Playwright's Chromium in CI; "not run" means no run has been recorded (the Firefox and WebKit projects run in the nightly workflow; pull-request CI skips them); "not done" means no session has been performed or recorded.
| Area | Chromium | Firefox | WebKit | Screen reader |
|---|---|---|---|---|
| Keyboard | tested in Chromium | not run | not run | not done |
| Focus | tested in Chromium | not run | not run | not done |
| axe scan | tested in Chromium | not run | not run | n/a |
| RTL | tested in Chromium (tabs) | not run | not run | not done |
| Without JavaScript | tested in Chromium | not run | not run | n/a |
| Reduced motion | stylesheet rule; no browser test | not run | not run | n/a |
| Forced colors | stylesheet rule; no browser test | not run | not run | n/a |
| Screen reader smoke | not done | not done | not done | not done |
Recorded Firefox and WebKit runs are a stage 2 requirement and a recorded screen-reader session per primitive (VoiceOver with Safari, NVDA with Chrome or Firefox) a stage 3 requirement in docs/release.md. Until they exist the site says "axe and keyboard tests", not "screen-reader tested", and no cross-browser statement is made.
What is not claimed
- Screen-reader behavior, in any browser.
- Behavior in Firefox or WebKit.
- IME composition handling: no primitive with a text input exists yet.
- Anything about a Tier 2 primitive: none exists.
Source
- apps/playground/tests/primitives: the browser suite
- a11y.spec.ts: the axe scans
- packages/primitives/src: one contract per primitive
- docs/proof/claims.md: the status of every claim