Accessibility
Accessibility in oriUI is structural, not a coat of polish on top. The same decisions that make the components correct for assistive technology also make them themeable and framework-agnostic — they are the same source of truth, not a separate effort.
State lives in attributes, not classes
Every dynamic state is a real DOM attribute, styled with attribute selectors — never a JavaScript-only class:
disabled/aria-disabledfor disabled controls,aria-busy="true"for loading,aria-pressed/data-activefor toggles,aria-expanded+aria-controlsfor disclosures.
This is the accessible source of truth, and because it is plain markup it is identical across all
three layers — a styled OriButton, a hand-written .ori-button, and a headless prop bag all expose
the same attributes to a screen reader.
Color meets WCAG AA — and it's tested
Color comes in role / on-role pairs (primary / on-primary, …), so foreground always has a
matching, legible background. That contract is executable: tests/tokens.contrast.test.ts parses
every skin's CSS and asserts each role/on-role pair clears the WCAG AA contrast ratio — in CI, for
every skin and both light and dark. It has already caught a real contrast failure in a skin during
development.
Keyboard and focus
- Controls render native elements (
<button>,<input>) wherever possible, so keyboard activation (Enter/Space), tab order, and form semantics come for free. - Focus rings ride
:focus-visible, so they show for keyboard users without flashing on every mouse click. - Hover styling is wrapped in
@media (hover: hover), so there is no sticky hover on touch. - The headless layer owns the hard keyboard behavior —
useDialog's focus trap,Escapeto close, and focus return to the trigger. See @oriui/headless.
Touch targets
The default control size (md) is a 44px target — a comfortable tap area on touch, matching the
mobile-first sizing of the token scale. Use the larger lg size where a screen calls for it.
Per-component contracts
Every component page has its own Accessibility section spelling out roles, states, and the keyboard map, and each component ships axe-core assertions in the test suite. Start with Button.