Accessibility
@arachnejs/ui targets WCAG 2.2 AA. Every showcase page is audited with axe in light and dark themes, and the customization contract test checks every component. This guide covers what the kit guarantees and what is still up to you.
Guides: getting started · customization · theming · accessibility · SSR & hydration · component reference
What the kit guarantees #
- Semantic markup. Buttons are
<button>, links are<a>, tables are<table>, and lists render real<ul>/<li>. Control flow renders into comment ranges rather than wrapper elements, so list and table structure stays valid. - Accessible names. Components that need a name have a sensible default (
Spinner→ "Loading",Pagination→ "Pagination",RingProgress→ "Progress", …). A caller'saria-labelalways wins, because defaults are placed before the attribute spread. Many components also take alabelprop for the name. - Keyboard models follow the WAI-ARIA Authoring Practices:
- Tabs: arrows, Home/End, automatic or manual activation.
- Menu: arrows, Home/End, type-ahead, Escape.
- ContextMenu: opens with Shift+F10 or the ContextMenu key; arrows and Escape.
- Tree: arrows and type-ahead.
- Calendar: a date grid with arrows, PageUp/PageDown and Home/End.
- Navbar: a menubar.
- Autocomplete, Rating, PinInput and Spotlight.
- CommandBar and FloatingToolbar: toolbars with roving focus.
- KanbanBoard: Alt+arrows move cards, and moves are announced.
- Focus management.
- Modal, Drawer, BottomSheet and ConfirmDialog trap focus and restore it on close.
- Escape closes only the topmost layer.
- Scroll lock compensates for the scrollbar width.
- Visible focus. Every interactive element shows a focus ring (
--a-focus-ring) for keyboard users, and scrollable regions (ScrollArea,Reel,CodeBlock) are keyboard-focusable. - Contrast.
- Text tokens meet 4.5:1 in both themes.
- Each tone has an
-inkpartner for text on solid fills. applyPalettepicks a readableaccentInkautomatically.
- Reduced motion. Under
prefers-reduced-motion: reduceall animations and transitions collapse, and overlays unmount without exit animations. - Live regions. Toasts,
KanbanBoardmoves and async states announce throughrole="status"/aria-live.
What is up to you #
-
Label every form control. Wrap it in
FormField(label+labelFor), which also linkshelpanderrortext witharia-describedby, or passaria-label:<FormField label="Email" labelFor="email" help="We never share it." error={error()}> <TextInput id="email" type="email" value={email()} /> </FormField> <SearchInput aria-label="Search projects" value={q()} onChange={setQ} /> -
Keep the heading outline. Components with titles take an
orderprop for the heading level;sizeis visual only. Examples:Title,Subtitle,FormSection,FormArea,ProductCard,ReviewCard,ArticleTitle. Pick the level that fits where the component sits on your page. -
Name repeated landmarks. When a page has several
Panels (each is a<nav>), give each alabel. Nest anAppShellinside an existing<main>withcontentAs="div". -
Give skip links a target.
SkipLinkdefaults to#main: putid="main"(andtabindex="-1") on your main landmark. -
Provide text alternatives. Set
altonImage,imageAltonProductCard, andbeforeAlt/afterAltonBeforeAfter. Leave them empty only for decorative images. -
Choose colours carefully. If you override tone tokens, re-check contrast, including the
-inkpartners.
Testing your app #
The kit's own checks are a good template:
- The contract test (
packages/ui/src/contract.test.ts) renders every example and asserts attribute forwarding and that a caller'saria-labelwins. - axe: run
axe-coreagainst your pages in a real browser, in both themes, after entry animations finish. - Test keyboard paths end to end (Tab order, Escape, arrows) in Playwright.