Accessibility
Accessibility conformance statement
An honest account of what Cronus UI targets, how that target is enforced, and where the gaps are. It is a self-assessment (ACR-style), not a certification.
Target standard
Cronus UI is built to meet WCAG 2.2 Level AA.
The design goal for every component is Web Content Accessibility Guidelines (WCAG) 2.2, Level AA. This statement describes our conformance effort and the checks behind it. It is a self-assessment produced by the maintainers — not a formal audit, and not a certification. Cronus UI is a component library, so the final accessibility of a shipped product also depends on how you compose these components, your content, and your app.
Self-assessment, not a certificate
How it is enforced
Conformance is not a one-time review — it is gated on every run by layered automated and manual checks.
Automated axe gate
e2e/a11y runs axe-core over every component and block route and fails the build on any serious or critical WCAG A/AA violation.
Keyboard e2e
e2e/flows/keyboard.spec.ts drives dialogs, menus, tabs, selects, sliders, switches, and accordions with real key events and asserts focus and state.
Unit axe layer
Component tests assert zero axe violations with vitest-axe through @cronus-ui/ui/testing, so regressions are caught before a route exists.
Reduced motion & RTL
Animated primitives honour prefers-reduced-motion by default, and layout uses logical properties so components mirror correctly under dir=rtl.
Contrast gate
bun run test:contrast (e2e/a11y/contrast-themes.spec.ts) runs a contrast-only axe scan over representative routes across all 10 theme × mode combinations.
The broad gate (bun run test:a11y) scans every /components/* and /blocks/* route with axe-core in aurora/dark and fails the build on any serious or critical violation. It runs the WCAG 2.0 and 2.1 Level A/AA rule sets — the subset that can be checked automatically — so it does not, on its own, prove every WCAG 2.2 criterion. bun run test:contrast then runs a contrast-only axe scan over representative routes in all 10 theme × mode combinations.
// e2e/a11y/core-routes.a11y.spec.ts — the automated gate.// Runs the WCAG 2.0 + 2.1 Level A/AA rule sets (the automatable subset)// over every component and block route, generated from routes.generated.json.const results = await new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]).analyze();// serious/critical are hard failures; moderate/minor are reported, not blocked.const blocking = results.violations.filter((v) => v.impact === "serious" || v.impact === "critical",);expect(blocking).toEqual([]);
Alongside it, the keyboard end-to-end suite drives the highest-value widgets with real key events and asserts focus location, aria-expanded, aria-checked, selected values, and focus return — the operability that a static scan cannot see.
What is supported
These behaviors are represented in components, and covered by the automated and keyboard gates.
- Keyboard operability follows the WAI-ARIA authoring pattern for each primitive.
- Focus is managed and trapped in overlays, and returns to the trigger on dismiss.
- Interactive elements expose an accessible name, role, and state.
- One skip link and exactly one <main> landmark per route (bypass blocks).
- Focus is visible via focus-visible in both dark and light presets.
- Semantic token pairs are authored to meet WCAG AA text contrast.
Known limitations & not-yet-covered
The honest half of a conformance statement. These are real gaps, stated plainly.
- Only serious and critical axe findings block; moderate and minor findings are reported, triaged, and fixed but do not fail the build.
- Automated scans cover the automatable subset of WCAG. jsdom cannot compute rendered contrast, so contrast is validated by the browser-based axe project and by authoring token pairs — not by the unit layer.
- The broad axe gate (bun run test:a11y) scans every component and block page in aurora/dark. bun run test:contrast sweeps representative routes across all 10 theme × mode combinations — a contrast-only axe scan, not every route × every combo.
- WCAG 2.2 adds success criteria (for example target size and focus-not-obscured) that have little automated coverage; those are addressed through the component patterns and manual review rather than a passing rule.
- Components that wrap third-party libraries (charts, some date primitives) inherit their upstream accessibility; we add names and text alternatives around them but do not rewrite their internals.
- No formal third-party accessibility audit has been performed yet, and no VPAT has been issued.
- Screen-reader behaviour is verified by manual passes, not by an automated gate, so coverage depends on the reviewer.
Contrast is browser-gated, not unit-gated
For teams adopting Cronus UI
The accessibility you get depends on how you use the components — here is how to keep it.
Whether you install through the registry or copy the source, you get the same component behavior — the accessibility work travels with the code, not a runtime service. To keep it as you compose and extend, run the same axe gate we do with @cronus-ui/ui/testing’s expectNoA11yViolations, and pair it with a keyboard and screen-reader pass on your own flows:
// your-component.test.tsx — the same axe gate we run, in your app.import { expectNoA11yViolations, renderWithCronus } from "@cronus-ui/ui/testing";import { screen } from "@testing-library/react";import userEvent from "@testing-library/user-event";import { it } from "vitest";it("stays accessible after interaction", async () => {const user = userEvent.setup();const { baseElement } = renderWithCronus(<YourComponent />);await user.click(screen.getByRole("button", { name: "Open" }));await expectNoA11yViolations(baseElement); // fails with the violations});
Provide accessible names for your content (labels for fields, aria-label for icon-only buttons), keep one <main> and a skip link per route, and move focus deliberately after navigation and async actions — the areas only your app can own.
Report an accessibility issue
Related
See the Accessibility overview for the per-component guidance, and RTL for the logical-properties and direction work referenced above.