Accessibility
Hololink UI targets WCAG 2.2 level AA. The components do the work that belongs in a component library. You do the work that depends on your content and your page.
This page explains the commitment. For key-by-key behaviour, see the Keyboard reference. Each component page also has its own Accessibility section.
What the components guarantee
Section titled “What the components guarantee”- Native semantics first. Components render native elements where one exists:
<button>,<a>,<input>,<select>,<textarea>,<details>,<dialog>,<table>,<hr>,<nav>. Where there is no native element (tabs, menu, radio group, switch), they set the matching ARIA role and state, and manage keyboard behaviour. - Visible focus. Interactive elements show a focus ring when focused with the keyboard. The ring uses
--s-color-border-interactiveand meets 3:1 against the surface and page background. - Visible control boundaries. Form controls, switch tracks and slider tracks use
--s-color-border-control, which meets 3:1 against the surface and background. Decorative borders (border-subtle,border-strong) are never the only boundary of a control. - Reduced motion. Transitions and animations (overlay fades, spinner rotation, skeleton shimmer, carousel sliding, switch thumb) only run under
prefers-reduced-motion: no-preference. - Contrast-tested token pairs. Semantic text and status colours meet 4.5:1 on the surfaces they are meant for, in both the light and dark themes. Control and focus colours meet 3:1.
- No colour-only meaning. The design rules forbid it. Alerts and toasts add an icon to the status colour, a switch shows state by thumb position as well as colour, and badges and tags rely on the text you write.
- Overlays behave like overlays. Dialog and Drawer use
showModal(), which traps focus, makes the page inert and closes onEsc. They return focus to the element that opened them. Popover and Menu use the Popover API for light dismiss. - Form participation. Form controls that are custom elements (Checkbox, Radio, Select, Slider, Switch, Text Field, Textarea) are form-associated through
ElementInternals, so they submit with the form and report validity.
What you remain responsible for
Section titled “What you remain responsible for”Components cannot know your content or your page. You provide:
- Accessible names. Give every form control a
label. Give every Icon Button a meaningfullabel. Give landmarks and groups a name where there is more than one (carousels, dialogs, tab lists, radio groups). - Headings and page structure. Use one
<h1>, keep heading levels in order, and use landmarks (<main>,<nav>,<header>). Note that some components render fixed heading levels (for example Accordion usesh3and Dialog usesh2). - Content. Write link text that makes sense out of context, error messages that explain how to fix the problem, and alternative text for images.
- Focus management in your app. For example, moving focus after removing a Tag, dismissing an Alert, or changing page in Pagination.
- Announcements. Components do not announce changes you make elsewhere. Use a live region when something changes that users would otherwise miss.
- Overrides. If you override
--s-*tokens or--holo-*hooks, you are responsible for keeping contrast and focus visibility. - Testing in your own context. Real content, real themes and real assistive technology can behave differently from a demo.
Known limits
Section titled “Known limits”These are current behaviours worth knowing about. They are also noted on the relevant component pages.
- Accordion headers are ordinary tab stops, with no arrow-key navigation between them.
- Carousel autoplay pauses on hover and focus but has no pause button.
- Form Field names its control with a native
<label for>, which only labels native controls and form-associated custom elements. A custom element that renders its input in its own shadow root, such as Text Field, needs its ownlabel. - Tooltip triggers must be in the same document or shadow root as the tooltip, because the
aria-describedbyid reference cannot cross a shadow-root boundary. - Popover sets
aria-expandedon its trigger but notaria-controls, because the panel is inside its shadow root. - Button does not forward ARIA attributes such as
aria-expandedoraria-describedbyto its inner<button>. Use a native<button>as the trigger for Popover and Tooltip. - Toast and the close buttons of Dialog and Drawer use fixed English labels (“Dismiss notification”, “Close dialog”, “Close drawer”). Alert, Tag, Pagination and Breadcrumb have label props.
How it is tested
Section titled “How it is tested”- Storybook with
@storybook/addon-a11y. The addon is registered globally and runs axe on every story using the WCAG rule tagswcag2a,wcag2aa,wcag21a,wcag21aaandwcag22aa. Stories cover variants and states, and are checked in both themes. - Token contrast unit tests.
packages/ui/src/styles/tokens.unit.test.tsparsestokens.css, resolvesvar()andlight-dark(), and asserts 4.5:1 for text pairs and 3:1 for control boundaries and focus colours, in light and dark. It runs in CI withpnpm test. - Keyboard passes. Each component gets a manual keyboard pass before merge: tab order, activation keys, arrow-key behaviour,
Escand focus return. - Component tests. Component tests check the roles, states and attributes that components set.
Automated tools find only part of the problems that matter. They do not replace testing with a keyboard and a screen reader.
- Keyboard reference
- Storybook to inspect every state with the Accessibility panel