Accessibility
Accessibility isn't an afterthought in Lerpa UI — it's enforced in the test suite. Every component targets WCAG 2.1 AA (many reach AAA) and is checked with axe on every change.
How it's verified
- Automated axe tests. Components run through
vitest-axe/axe-corein jsdom, so violations fail CI. - Semantic markup. Real
<button>,<nav>,<main>, and<aside>landmarks instead of<div onClick>. - Visible focus. Focus rings are preserved — never removed via
outline: nonewithout a replacement. - Contrast. The token palette is tuned for ≥4.5:1 on body text and ≥3:1 on UI chrome, across every theme.
Reduced motion
Every animated component checks the user's motion preference. Set the OS "reduce motion" flag and animations stop — no flicker, no rebuild. The library exposes a hook for this; in the docs app a <MotionConfig reducedMotion="user"> wraps the tree.
import { usePrefersReducedMotion } from "@lerpa/ui";
function Fade() {
const reduced = usePrefersReducedMotion();
return (
<motion.div
initial={reduced ? { opacity: 1 } : { opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={reduced ? { duration: 0 } : { duration: 0.5 }}
/>
);
}Keyboard
Interactive components are keyboard-first: roving focus, Tab / Shift+Tab order, Escape-to-close on overlays, and arrow-key navigation in menus and command palettes. Where an interactive <div> is unavoidable, it carries role="button", tabIndex=0, and keyboard handlers.
Production primitives
For form controls where native semantics matter most, Lerpa UI ships accessible, production-grade primitives:
- Checkbox
- Proper checked / indeterminate states and label association.
- Select
- Keyboard navigation, typeahead, and correct ARIA listbox roles.
- Combobox
- Filterable options with announced active descendants.
- Datepicker
- Grid semantics with arrow-key date traversal.
See the live commitments and rule list on the accessibility page.