What Proper UI's accessibility testing actually covers: automated axe-core checks in CI, what React Aria provides, and what composition still requires human review.
The claim, precisely
Every component group in packages/ui/src/components ships a test suite that renders each documented variant and asserts zero detected axe-core violations. As of this page, that's 118 test suites, all 118 asserting toHaveNoViolations, running on every pull request in CI.
That number is real, and it's also narrow. The W3C is direct about the ceiling: "Tools cannot check all accessibility aspects automatically. Human judgement is required." Automated tools "can not determine accessibility, they can only assist in doing so." This page exists so the number links to its own boundary instead of standing in for a claim the tests don't make.
What is automated
Each suite lives next to its component (button.test.tsx, modals.test.tsx, table.test.tsx, and so on) and follows the same shape: render every demo export, run axe(container) from vitest-axe inside jsdom, and assert the result toHaveNoViolations(). packages/ui/vitest.setup.ts wires the matcher globally and patches in the DOM APIs jsdom lacks (matchMedia, ResizeObserver, IntersectionObserver) so components that measure their own layout don't crash — it does not, and cannot, patch in a real rendering engine.
This runs on every PR, so a regression that introduces a detectable violation fails CI before merge. Concretely, axe-core in this setup checks things like:
- Every interactive element has an accessible name.
- ARIA attributes are valid, on the correct role, and don't contradict each other.
- Form fields have programmatically associated labels.
- Heading levels and landmark roles present in the rendered markup are structured correctly relative to each other.
- Roles that require specific children or parents (menus, listboxes, grids) have them.
What it does not, and structurally cannot, check:
- Meaningful alt text. Axe confirms an
<img>has analtattribute; it has no way to judge whetheralt="image"is worse than no image at all. - Focus order sensibility. It can confirm elements are focusable and in the accessibility tree; whether tabbing through a page visits things in an order a person would consider sane is not something a DOM snapshot answers.
- Colour contrast, in this setup specifically. Axe's contrast checks work by rendering; jsdom does no layout or paint, so there is no computed style, no
getBoundingClientRect, and no way for axe to sample a background against a foreground. The rule isn't disabled, it simply never has anything to measure in this test environment. - Screen-reader announcement quality. Correct ARIA (roles, states,
aria-live) doesn't guarantee an announcement makes sense out loud, in the order it fires, at the verbosity a screen-reader user wants. - Keyboard operability beyond static ARIA. Axe reads the DOM as rendered; it doesn't press keys. Of the 118 suites, only
header-navigations.test.tsxsimulates a keyboard interaction (opening a menu, then dismissing it withEscape, viafireEvent.keyDown). Every other suite verifies the ARIA is correct in a single static render — not that arrow keys,Home/End, typeahead, or focus trapping behave correctly when actually driven.
What React Aria provides
The keyboard and screen-reader behaviour the axe suites don't test for is largely the job of a different layer: React Aria Components, which every interactive Proper UI component (menus, dialogs, comboboxes, tables, date pickers, sliders) is built on rather than reimplementing from scratch.
React Aria's own documentation is specific about what it takes on:
- Keyboard navigation. "Arrow key navigation, typeahead, multiple selection modifiers, landmark navigation, and much more" — keyboard interactions are described as first-class, not an add-on.
- Focus management. "Focus is automatically contained within overlays, restored on close, and moved when list items are deleted. Focus rings appear only when using the keyboard."
- ARIA wiring. Components "implement semantics and keyboard behavior according to the W3C ARIA Authoring Practices Guide," with React Aria adding "real-world testing and device support" on top of the spec.
- Screen-reader behaviour. React Aria states its behaviors "work without a keyboard, ensuring touch screen reader users have full access," and that components are "extensively tested using many popular screen readers and devices" — testing this project's own suites don't reproduce.
- Internationalization and RTL. Translations in 30+ languages, localized date/number formatting across 13 calendar systems, and right-to-left layout support are built in — see the RTL support page for how far Proper UI's own styling layer has followed that support.
In short: the axe suites verify the ARIA output is well-formed at render time; React Aria is the reason there's correct keyboard and screen-reader behaviour underneath it for the suites to render in the first place. Neither one substitutes for the other, and neither one is a claim of "fully accessible."
What is on you
React Aria gives every primitive correct interaction behaviour, and the axe suites catch the class of markup mistakes that are mechanically detectable. Neither one reviews how you compose those primitives into a page. Specifically still your responsibility:
- Labels for icon-only controls. A
Buttonwith only an icon child needs an explicitaria-label— nothing in the component can infer what the icon means. - Contrast of custom brand colours. The shipped tokens in
theme.cssmeet WCAG 2.1 AA in both themes; if you override a brand colour, you own re-checking contrast, since jsdom cannot verify it for you (see above) and nothing else in the pipeline will. - Heading order and landmark structure across a composed page. A single component's demo can be internally well-structured while the page you build from several of them skips a heading level or nests two
<main>landmarks. Axe only ever sees one rendered tree at a time — the composition is yours to check. - Alt text for the images you add. The library doesn't ship your images or copy.
- Motion preferences. Components with transitions don't unilaterally decide what
prefers-reduced-motionshould do in your product.
This applies whether a human or an AI assistant is writing the composition. An assistant generating a page from these primitives can still put a heading level out of order, nest landmarks incorrectly, or leave an icon button unlabeled — the primitives being sound doesn't make the assembly automatically sound. Generated or hand-written, page composition is something that still gets reviewed, not something the 118 green suites already cover.
Manual verification matrix
No manual assistive-technology audit has been performed on these components yet. The table below is the honest state of that work — every cell is unverified, not passing:
| Component | Keyboard | VoiceOver / macOS | NVDA / Windows | Touch |
|---|---|---|---|---|
| Menus / dropdowns | Not yet verified | Not yet verified | Not yet verified | Not yet verified |
| Modals / dialogs | Not yet verified | Not yet verified | Not yet verified | Not yet verified |
| Comboboxes / selects | Not yet verified | Not yet verified | Not yet verified | Not yet verified |
| Date pickers | Not yet verified | Not yet verified | Not yet verified | Not yet verified |
| Tables | Not yet verified | Not yet verified | Not yet verified | Not yet verified |
| Tabs | Not yet verified | Not yet verified | Not yet verified | Not yet verified |
| Sliders | Not yet verified | Not yet verified | Not yet verified | Not yet verified |
| File upload | Not yet verified | Not yet verified | Not yet verified | Not yet verified |
This matrix will be filled in, component by component, as real manual verification happens — not as a one-time pass, since a regression can reopen a cell that was once checked. If you've run one of these components through an actual screen reader or keyboard-only pass, open an issue or a pull request with what you found; contributions here are the fastest way this table stops being all "not yet verified."
The wording rule
Every mention of this testing across the site, marketing included, follows one rule: say "0 detected axe violations in 118 automated suites," never "fully accessible." The first sentence is a fact about a specific, named test. The second is a claim no automated suite, and no library, gets to make on your behalf.
Known limitations
- RTL is infrastructure-complete, styling-incomplete. React Aria's direction handling (arrow-key navigation, popover placement, field order) is done. The Tailwind logical-properties migration in
packages/ui/src/componentsis not — see RTL support for the current counts. A component that hasn't migrated will not mirror correctly in adir="rtl"document. - No contrast testing. As noted above, jsdom performs no layout, so axe's colour-contrast rule has nothing to sample against in this test environment. Contrast is currently guaranteed only by the shipped token values, not by an automated check.
- 118 suites test render output, not interaction, almost everywhere. Aside from the one keyboard-driven suite noted earlier, "no violations" describes a single static render of each demo — not a full session of tabbing, arrowing, and dismissing through it.
- No visual regression baseline. Per the project roadmap, the screenshot tooling exists but has no committed baseline and no CI job, so a visual accessibility regression (e.g., a focus ring silently removed) would not be caught by any automated check today.