Accessibility
thl-ui targets WCAG 2.1 AA. Every page of the three playgrounds (75 routes × React / Vue / Angular, in light and dark mode) is audited with axe-core, and none has a serious or critical violation.
sh
node scripts/a11y.mjs http://localhost:5181 # React, fails on serious/critical
node scripts/a11y.mjs http://localhost:5182 --dark # Vue, dark mode
node scripts/a11y.mjs http://localhost:5183 --min moderate --json report.jsonWhat components do for you
| Area | Behavior |
|---|---|
| Color | Every text/background pair is ≥ 4.5:1 (see accessible color pairs); bright fills get dark text |
| Forms | Field wires <label for>, aria-describedby (help / error) and aria-invalid to whatever control you put inside, including custom widgets via aria-labelledby. Errors use role="alert" and are translated |
| Checkbox / Switch | Native inputs, so they work with keyboard and forms. The label text is the accessible name; ariaLabel is available for icon-only use |
| Select | Combobox pattern: role=combobox + listbox, aria-activedescendant, arrow keys / Enter / Esc, type-ahead search |
| Date / time pickers | Trigger is a combobox with aria-haspopup="dialog" and aria-expanded; the calendar grid has full-date aria-labels |
| Slider | role=slider with value / min / max / valuetext; arrow keys, Home / End, PageUp / PageDown; range thumbs are named "Minimum" / "Maximum" |
| Tabs | Roving tabindex, arrow keys skip disabled tabs, Home / End, aria-controls / aria-labelledby |
| Modal / Drawer | aria-modal, focus trap, focus restore, Esc, scroll lock; a closed drawer is inert |
| Dropdown / Popover | aria-haspopup / aria-expanded / aria-controls on your trigger element, not a wrapper; Esc returns focus |
| Tooltip | Shows on hover and focus, linked with aria-describedby |
| Progress | role=progressbar with value and a localized name |
| Layout | Skip link, header / nav / main / footer landmarks, aria-current="page" in the side menu, h1 page titles |
| Motion & RTL | Logical CSS properties, so dir="rtl" mirrors the layout; :focus-visible rings use the theme color |
Your responsibilities
- Give icon-only buttons an
aria-label:<ThlButton circle icon="heart" aria-label="Like" />. - Put controls inside
Fieldwith alabel, or give them anaria-label. - Scrollable regions you build yourself need
tabindex="0"and a label. - Custom colors: keep text ≥ 4.5:1. Override both
--thl-color-xand--thl-on-x/--thl-color-x-texttogether.