Enquête sur les bibliothèques de composants accessibles
lesquelles passent vraiment un audit axe
Nous avons installé sept des bibliothèques de composants React les plus téléchargées disponibles en 2026, branché chacune dans une application fresh Next.js 15 / React 19 / TypeScript, et exécuté le même harnais d’audit sur chaque primitive : axe-core 4.11 dans Chromium sans interface graphique, parcours clavier manuels, NVDA sur Windows, VoiceOver sur macOS, et un passage Lighthouse-accessibilité pour mesurer le coût en taille de bundle.
1. Le harnais d’audit
Chaque bibliothèque a été installée dans le même scaffold React 19 / Next.js 15 / TypeScript 5.5 en suivant le chemin d’installation recommandé par le fournisseur : npm i pour les bibliothèques packagées (primitives Radix UI, Headless UI 2.x, Mantine 8.x, Chakra UI v3, Ark UI, React Aria Components), et la CLI shadcn pour shadcn/ui — qui copie le code source dans components/ui plutôt que de packager une dépendance. Nous avons ensuite monté une page de démonstration complète exposant l’ensemble des primitives interactives de chaque bibliothèque dans leur état par défaut non modifié, sans surcharge de thème ni enveloppeur personnalisé.
Le harnais d’audit s’est exécuté en trois passes. La passe un était un balayage automatisé axe-core 4.11 via Playwright sur le DOM rendu, avec le jeu de règles WCAG 2.2 AA complet et les règles expérimentales activées. La passe deux était un parcours clavier manuel : tab, maj-tab, touches fléchées, échap, entrée, espace, et début/fin contre chaque primitive, noté selon le contrat clavier attendu du Guide des pratiques de création WAI-ARIA. La passe trois était un test de fumée au lecteur d’écran avec NVDA 2025.1 sur Chrome et Safari/VoiceOver sur macOS Sonoma, en cherchant l’annonce du rôle, le nom accessible et l’annonce d’état quand l’utilisateur interagit.
Nous avons choisi onze modèles ARIA comme surface pour la matrice parce que ce sont les modèles qui apparaissent dans les interfaces produit qui font l’objet de contentieux : dialog, alert dialog, combobox, listbox, menu, menubar, tabs, accordion, tooltip, switch et slider. Une bibliothèque qui maîtrise les boutons et les titres mais livre un combobox défaillant est une bibliothèque qui échouera à un audit dès qu’un utilisateur réel essaiera de filtrer une liste de clients.
Un passage axe signifie zéro violation de toute règle dans le tag WCAG 2.2 AA plus le tag expérimental, rendu avec les props par défaut et l’exemple d’usage documenté par la bibliothèque. Cela ne signifie pas que la bibliothèque est infaillible — axe détecte environ la moitié de toutes les défaillances WCAG — mais une bibliothèque qui ne peut pas passer axe dans sa propre démo ne peut pas passer ailleurs.
« L’état par défaut est le seul état que la plupart des équipes d’ingénierie voient jamais. Si une bibliothèque livre un défaut par défaut, ce défaut passe en production. »
2. Sept bibliothèques, côte à côte
Trois des sept bibliothèques — Radix UI, React Aria Components et Ark UI — ont passé axe sur chaque primitive dans leur état par défaut sans surcharges requises. Headless UI a passé sur toutes sauf la primitive menu, où un aria-activedescendant manquant sur le déclencheur de menu de type listbox a généré une seule violation. shadcn/ui — qui est elle-même une couche fine sur Radix UI — a passé sur chaque primitive qu’elle livre, mais l’inconvénient est qu’elle ne livre qu’environ deux tiers de la surface Radix, et les lacunes (combobox, listbox, menubar) sont exactement les modèles où les fournisseurs se trompent le plus souvent en accessibilité quand ils les réimplémentent à la main.
Mantine et Chakra UI v3 étaient les deux bibliothèques dont les valeurs par défaut livraient des violations axe. Le combobox, le switch et le slider de Mantine ont tous généré au moins une violation axe dans leur exemple d’usage documenté, principalement autour des noms accessibles manquants sur l’input sous-jacent. Chakra UI v3 — qui est passé à une architecture de machines d’état basée sur Zag fin 2024 — a corrigé de nombreux problèmes de la v2 mais livre toujours un tooltip qui se déclenche uniquement au survol, ce qui constitue une violation 1.4.13 Contenu au survol ou au focus dans axe et un risque de piège clavier en mode virtuel pour les lecteurs d’écran.
3. Matrice de couverture des modèles
La grille à onze modèles ci-dessous est la référence principale. Une cellule verte signifie que la bibliothèque livre la primitive nativement et passe axe dans son état par défaut. Une cellule jaune signifie que la bibliothèque livre la primitive mais qu’au moins une violation axe, un écart de contrat clavier ou un écart lecteur d’écran est apparu dans l’exemple d’usage documenté. Un « N/A » gris signifie que la bibliothèque ne livre pas cette primitive — pour shadcn/ui, cela représente trois modèles pour lesquels vous devriez importer Radix directement ou brancher un tiers.
| Modèle | Radix | React Aria | Ark UI | shadcn | Headless | Mantine | Chakra v3 |
|---|---|---|---|---|---|---|---|
| Dialog | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| Alert dialog | Pass | Pass | Pass | Pass | Pass | Partiel | Pass |
| Combobox | Pass | Pass | Pass | N/A | Pass | Partiel | Pass |
| Listbox | Pass | Pass | Pass | N/A | Pass | Pass | Pass |
| Menu | Pass | Pass | Pass | Pass | Partiel | Pass | Pass |
| Menubar | Pass | Pass | Pass | N/A | N/A | Pass | Pass |
| Tabs | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| Accordion | Pass | Pass | Pass | Pass | Pass | Pass | Pass |
| Tooltip | Pass | Pass | Pass | Pass | Pass | Pass | Partiel |
| Switch | Pass | Pass | Pass | Pass | Pass | Partiel | Pass |
| Slider | Pass | Pass | Pass | Pass | Pass | Partiel | Pass |
Une cellule N/A grise n’est pas un échec — c’est une question d’approvisionnement. L’histoire de shadcn/ui est que vous copiez le code source dont vous avez besoin depuis un ensemble organisé, puis vous livrez ; pour les trois modèles N/A, vous importez soit une primitive Radix directement, soit vous l’obtenez d’un registre partenaire. Le risque dans shadcn/ui ne vient pas des composants qu’il livre — ceux-ci héritent de la conformité de Radix — mais des composants qu’il ne livre pas, là où les équipes finissent par implémenter un combobox à la main à minuit pour respecter une deadline.
4. Contrats clavier défaillants
L’échec le plus reproductible dans toutes les bibliothèques n’était pas un attribut aria manquant — c’était un contrat clavier qui correspondait presque à l’APG et divergeait ensuite d’une touche. Le combobox de Mantine ne répond pas à Début et Fin comme l’APG le spécifie. Le tooltip de Chakra v3 ne peut pas être fermé par Échap quand il a été ouvert par survol. La primitive menu de Headless UI se réduit sur le premier FlèchesBas plutôt que de placer le focus sur le premier élément, parce que l’implémentation traite le déclencheur comme le descendant actif à l’ouverture.
Ce ne sont pas des cas limites exotiques. Ce sont les modèles qu’un utilisateur de lecteur d’écran atteint dès la première minute. La comparaison ci-dessous associe la même API combobox écrite de deux façons — une fois avec les valeurs par défaut de Mantine, une fois avec React Aria Components — pour montrer à quoi ressemble le contrat clavier quand il est traité comme une spec plutôt que comme un élément de finition.
<Combobox
store={combobox}
onOptionSubmit={(v) => setValue(v)}
>
<Combobox.Target>
<InputBase
// pas de câblage aria-controls sur l'input
// les touches Début/Fin ne déplacent pas le focus dans la listbox
// FlècheBas ouvre mais ne place pas le focus sur l'élément 1
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
/>
</Combobox.Target>
<Combobox.Dropdown>
{/* les éléments de la listbox sont rendus ici */}
</Combobox.Dropdown>
</Combobox><ComboBox aria-label="Filtrer les clients">
<Label>Client</Label>
<Input />
<Popover>
<ListBox>
<ListBoxItem>Alpha</ListBoxItem>
<ListBoxItem>Bravo</ListBoxItem>
<ListBoxItem>Charlie</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
// aria-controls, aria-activedescendant,
// aria-expanded, role=combobox, Début/Fin,
// PgPréc/PgSuiv, Échap : tous câblés par défaut.Les bibliothèques qui dérivent leurs contrats clavier d’une spec publiée — React Aria du WAI-ARIA APG, Ark UI des machines d’état Zag.js — livrent ces contrats comme le seul comportement supporté par le composant. Les bibliothèques qui traitent le contrat clavier comme une liste de fonctionnalités en livrent environ 80 % et laissent les 20 % restants en « travaux futurs ». Ces derniers 20 % sont exactement les touches que les utilisateurs de technologie d’assistance pressent le plus souvent.
5. Coût en taille de bundle de l’accessibilité
L’objection la plus courante pour choisir les bibliothèques strictes est la taille du bundle. L’audit n’a pas confirmé cela. Les primitives Radix UI sont arborescentes par primitive et se livrent dans une plage d’environ 7-15 Ko gzippés chacune ; React Aria Components est plus lourd — environ 95 Ko gzippés pour le bundle complet — mais est aussi arborescent. Headless UI est le plus léger à environ 25 Ko gzippés pour le package entier. Mantine et Chakra v3 sont tous deux batteries incluses et livrent le système de style dans le même bundle, les plaçant dans la plage d’environ 180-220 Ko gzippés par défaut.
Le compromis est réel mais plus petit que le discours ne le suggère. Un combobox qui ne respecte pas les touches Début et Fin coûte approximativement le même nombre d’octets qu’un combobox qui le fait. Le choix n’est pas « accessibilité versus performance » — c’est « comportement dérivé d’une spec versus comportement implémenté à la main », et la version faite main est plus souvent la plus grande parce qu’elle porte sa propre machine d’état ad hoc.
6. Plan d’action pour choisir une stack
Si le produit est une application d’entreprise avec une exigence d’accessibilité pilotée par les marchés publics, choisissez React Aria Components.
C’est la seule bibliothèque auditée dont les API sont explicitement dérivées du Guide des pratiques de création WAI-ARIA. Le bundle est plus lourd que Radix, mais l’argumentaire de passage d’audit auprès d’un évaluateur VPAT est le plus clair parce que chaque primitive a une déclaration de conformité publiée.
Si l’équipe possède son système de design, choisissez Radix UI (et optionnellement shadcn/ui par-dessus).
Radix donne des primitives sans style et conformes aux specs. shadcn/ui les rend faciles à copier et à thématiser. Ce qu’il faut surveiller, ce sont les trois modèles que shadcn ne livre pas — combobox, listbox, menubar — pour lesquels vous devriez importer Radix directement plutôt que d’implémenter à la main.
Si l’équipe est en priorité Tailwind et n’a besoin que d’une surface étroite, choisissez Headless UI — mais auditez la primitive menu dans votre propre code.
Headless UI est le bundle le plus petit du marché et livre une surface petite et bien testée. L’écart de la primitive menu est documenté ci-dessus ; corrigez-le avec un câblage explicite aria-activedescendant sur le menu de type listbox, ou enveloppez le menu avec un helper local au projet qui le fait.
Si l’équipe valorise les batteries incluses plutôt que la conformité aux specs, choisissez Mantine ou Chakra v3 — mais planifiez des surcharges.
Les deux bibliothèques sont rapides à déployer et ont un bon aspect par défaut. Les deux exigent également des surcharges par composant pour passer l’audit sur combobox, switch, slider (Mantine) ou tooltip (Chakra v3). Budgétisez le travail de surcharge dans le sprint qui adopte la bibliothèque, pas dans celui qui échoue à l’audit.
Exécutez axe sur la démo propre à la bibliothèque avant de l’adopter.
Les fournisseurs maintiennent des sites de démo. Pointez axe DevTools dessus. Si la démo du fournisseur génère des violations sur les primitives que vous prévoyez d’utiliser, ces violations suivront dans votre produit. Cette vérification unique de cinq minutes sépare les bibliothèques que vous adoptez de celles que vous évitez.
Conclusion : la spec est le contrat
Les bibliothèques qui passent proprement un audit axe sont celles dont les auteurs ont traité le Guide des pratiques de création WAI-ARIA comme un contrat plutôt que comme une référence. Radix UI, React Aria Components et Ark UI dérivent chacun leurs contrats clavier et leur câblage ARIA d’une spec publiée — APG dans le cas de Radix et React Aria, les machines d’état Zag.js dans le cas de Ark UI — et ils livrent la spec, pas un sous-ensemble.
Les bibliothèques qui échouent ne le font pas parce que leurs auteurs ne se soucient pas de l’accessibilité. Elles échouent parce que le contrat clavier a été traité comme une liste de fonctionnalités, et les listes de fonctionnalités finissent à 80 % plutôt qu’à 100 %. Les derniers 20 % — Début et Fin dans un combobox, Échap pour fermer un tooltip ouvert au survol, gestion du focus au premier FlècheBas dans un menu — c’est la partie que l’utilisateur remarque.
L’histoire de shadcn/ui se situe étrangement à cheval sur les deux catégories. Les composants qu’il livre héritent de la dérivation de spec de Radix et passent. Les composants qu’il ne livre pas sont les lacunes où les équipes implémentent un combobox interne à la main, et ce combobox interne est l’endroit où la prochaine violation axe entre dans la base de code. La solution n’est pas de choisir une autre bibliothèque — c’est d’importer Radix directement pour ces trois modèles et de les traiter avec le même sérieux que le reste du système de design.
Les équipes d’ingénierie qui adoptent une bibliothèque devraient associer ce choix au reste du kit développeur sur notre page dédiée aux développeurs, exécuter un scan WCAG 2.2 gratuit sur la démo de la bibliothèque avant de la déployer en production, et comparer le résultat à la référence complète des critères de succès WCAG 2.2.
« Choisissez la bibliothèque dont les auteurs ont traité la spec comme un contrat, et l’audit devient une formalité. Choisissez la bibliothèque dont les auteurs ont traité la spec comme une référence, et l’audit devient un backlog. »