Onderzoek naar toegankelijke componentbibliotheken
welke slagen voor een axe-audit
We installeerden zeven van de meest gedownloade React-componentbibliotheken die in 2026 worden uitgebracht, koppelden elk aan een verse Next.js 15 / React 19 / TypeScript-app, en voerden hetzelfde auditharnas uit op elke primitief: axe-core 4.11 in headless Chromium, handmatige toetsenbordcontroles, NVDA op Windows, VoiceOver op macOS, en een Lighthouse-toegankelijkheidscontrole voor de bundelomvangkosten.
1. Het auditharnas
Elke bibliotheek werd geïnstalleerd in hetzelfde React 19 / Next.js 15 / TypeScript 5.5-basisproject via het door de leverancier aanbevolen installatiepad: npm i voor de verpakte bibliotheken (Radix UI primitieven, Headless UI 2.x, Mantine 8.x, Chakra UI v3, Ark UI, React Aria Components), en de shadcn-CLI voor shadcn/ui — die broncode kopieert naar components/ui in plaats van een pakket te vendoren. Vervolgens monteerden we een kitchen-sink-demopagina die de volledige set interactieve primitieven van elke bibliotheek blootstelde in hun standaard, onbewerkte staat, zonder thema-overschrijdingen en zonder aangepaste wrappers.
Het auditharnas liep in drie rondes. Ronde één was een geautomatiseerde axe-core 4.11-scan via Playwright op de gerenderde DOM, met de volledige WCAG 2.2 AA-regelset en de experimentele regels ingeschakeld. Ronde twee was een handmatige toetsenbordcontrole: tab, shift-tab, pijltoetsen, escape, enter, spatie en home/end op elke primitief, beoordeeld aan de hand van het verwachte toetsenbordcontract van de WAI-ARIA Authoring Practices Guide. Ronde drie was een schermlezer-rooktest met NVDA 2025.1 op Chrome en Safari/VoiceOver op macOS Sonoma, waarbij werd gekeken naar de rolmelding, de toegankelijke naam en de statusmelding wanneer de gebruiker interacteert.
We kozen elf ARIA-patronen als het oppervlak voor de matrix, omdat dit de patronen zijn die in product-UI’s verschijnen die worden aangeklaagd: dialoogvenster, waarschuwingsdialoogvenster, combobox, listbox, menu, menubalk, tabbladen, accordeon, tooltip, schakelaar en schuifregelaar. Een bibliotheek die knoppen en koppen perfect afhandelt maar een kapotte combobox levert, is een bibliotheek die een audit zal falen zodra een echte gebruiker een klantenlijst probeert te filteren.
Een axe-doorgang betekent nul schendingen van een regel in de WCAG 2.2 AA-tag plus de experimentele tag, gerenderd met standaardeigenschappen en het gedocumenteerde gebruiksvoorbeeld van de bibliotheek. Het betekent niet dat de bibliotheek kogelvrij is — axe onderschept ongeveer de helft van alle WCAG-fouten — maar een bibliotheek die axe niet haalt in haar eigen demo haalt het nergens anders.
”De standaardstaat is de enige staat die de meeste engineeringteams ooit zien. Als een bibliotheek een kapotte standaard levert, gaat de kapotte standaard naar productie.”
2. Zeven bibliotheken naast elkaar
Drie van de zeven bibliotheken — Radix UI, React Aria Components en Ark UI — slaagden voor axe op elke primitief in hun standaardstaat zonder vereiste overschrijdingen. Headless UI slaagde op alle behalve de menuprimtief, waarbij een ontbrekende aria-activedescendant op de listbox-stijl menutrigger één schending gaf. shadcn/ui — dat zelf een dunne laag op Radix UI is — slaagde op elke primitief die het levert, maar de kanttekening is dat het slechts ongeveer tweederde van het Radix-oppervlak levert, en de hiaten (combobox, listbox, menubalk) zijn precies de patronen waarbij leveranciers toegankelijkheid het vaakst verkeerd doen als ze die handmatig opnieuw implementeren.
Mantine en Chakra UI v3 waren de twee bibliotheken waarbij de standaarden axe-schendingen opleverden. Manteines combobox, schakelaar en schuifregelaar genereerden elk ten minste één axe-schending in hun gedocumenteerde gebruiksvoorbeeld, voornamelijk rond ontbrekende toegankelijke namen op de onderliggende invoer. Chakra UI v3 — dat in eind 2024 overstapte op een Zag-gebaseerde toestandsmachine-architectuur — loste veel v2-problemen op, maar levert nog steeds een tooltip die alleen bij hover wordt geactiveerd, wat een 1.4.13 Inhoud bij aanwijzen of focus-schending is in axe en een toetsenbordvalrisico in virtuele schermlezer-modus.
3. Patroondekkingsmatrix
Het onderstaande elf-patronenraster is de hoofdreferentie. Een groene cel betekent dat de bibliotheek de primitief natiefs levert en axe doorstaat in de standaardstaat. Een gele cel betekent dat de bibliotheek de primitief levert, maar dat er ten minste één axe-schending, toetsenbordcontractkloof of schermlezerkloof verscheen in het gedocumenteerde gebruiksvoorbeeld. Een grijze “N/A” betekent dat de bibliotheek die primitief niet levert — voor shadcn/ui zijn dat drie patronen waarbij u Radix rechtstreeks zou moeten importeren of een derde partij zou moeten inzetten.
| Patroon | Radix | React Aria | Ark UI | shadcn | Headless | Mantine | Chakra v3 |
|---|---|---|---|---|---|---|---|
| Dialoogvenster | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang |
| Waarschuwingsdialoogvenster | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Gedeeltelijk | Doorgang |
| Combobox | Doorgang | Doorgang | Doorgang | N/A | Doorgang | Gedeeltelijk | Doorgang |
| Listbox | Doorgang | Doorgang | Doorgang | N/A | Doorgang | Doorgang | Doorgang |
| Menu | Doorgang | Doorgang | Doorgang | Doorgang | Gedeeltelijk | Doorgang | Doorgang |
| Menubalk | Doorgang | Doorgang | Doorgang | N/A | N/A | Doorgang | Doorgang |
| Tabbladen | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang |
| Accordeon | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang |
| Tooltip | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Gedeeltelijk |
| Schakelaar | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Gedeeltelijk | Doorgang |
| Schuifregelaar | Doorgang | Doorgang | Doorgang | Doorgang | Doorgang | Gedeeltelijk | Doorgang |
Een grijze N/A-cel is geen fout — het is een inkoopvraag. Het verhaal van shadcn/ui is dat men de benodigde broncode kopieert uit een samengestelde set en vervolgens levert; voor de drie N/A-patronen importeert men ofwel een Radix-primitief rechtstreeks of haalt men het uit een zusterregister. Het risico bij shadcn/ui zijn niet de componenten die het levert — die erven Radix’s conformiteit — het zijn de componenten die het niet levert, waarbij teams uiteindelijk ‘s nachts een combobox handmatig implementeren om een deadline te halen.
4. Toetsenbordcontracten die braken
De meest herhaalde fout over bibliotheken heen was geen ontbrekend aria-attribuut — het was een toetsenbordcontract dat het APG bijna matched en dan met één toets afweek. Manteines combobox reageert niet op Home en End zoals de APG specificeert. Chakra v3’s tooltip kan niet worden gesloten met Escape wanneer hij door hover werd geopend. Headless UI’s menuprimtief klapt in bij de eerste ArrowDown in plaats van de focus op het eerste item te plaatsen, omdat de implementatie de trigger als de actieve afstammeling bij openen behandelt.
Dit zijn geen exotische randgevallen. Het zijn de patronen die een schermlezergebruiker in de eerste minuut bereikt. De vergelijking hieronder koppelt dezelfde combobox-API op twee manieren geschreven — eenmaal met de standaarden van Mantine, eenmaal met React Aria Components — om te laten zien hoe het toetsenbordcontract eruitziet wanneer het wordt behandeld als een specificatie in plaats van als een afwerkingsitem.
<Combobox
store={combobox}
onOptionSubmit={(v) => setValue(v)}
>
<Combobox.Target>
<InputBase
// geen aria-controls-koppeling op de invoer
// Home/End-toetsen verplaatsen focus niet in de listbox
// ArrowDown opent maar plaatst focus niet op item 1
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
/>
</Combobox.Target>
<Combobox.Dropdown>
{/* listbox-items worden hier gerenderd */}
</Combobox.Dropdown>
</Combobox><ComboBox aria-label="Klanten filteren">
<Label>Klant</Label>
<Input />
<Popover>
<ListBox>
<ListBoxItem>Alfa</ListBoxItem>
<ListBoxItem>Bravo</ListBoxItem>
<ListBoxItem>Charlie</ListBoxItem>
</ListBox>
</Popover>
</ComboBox>
// aria-controls, aria-activedescendant,
// aria-expanded, role=combobox, Home/End,
// PageUp/PageDown, Escape: standaard allemaal gekoppeld.Bibliotheken die hun toetsenbordcontracten afleiden van een gepubliceerde specificatie — React Aria van de WAI-ARIA APG, Ark UI van de Zag.js-toestandsmachines — leveren die contracten als het enige gedrag dat de component ondersteunt. Bibliotheken die het toetsenbordcontract als een functieoverzicht behandelen, leveren er ruwweg 80% van en laten de laatste 20% als “toekomstig werk” over. Die laatste 20% zijn precies de toetsen die hulptechnologiegebruikers het meest indrukken.
5. Bundelomvangkosten van toegankelijkheid
Het meest voorkomende bezwaar tegen het kiezen van de strikte bibliotheken is bundelomvang. De audit onderschreef dit niet. Radix UI-primitieven zijn per-primitief tree-shakeable en worden geleverd in het bereik van circa 7-15 KB gezipt per stuk; React Aria Components is zwaarder — circa 95 KB gezipt voor de volledige bundel — maar is ook tree-shakeable. Headless UI is het lichtst met circa 25 KB gezipt voor het volledige pakket. Mantine en Chakra v3 zijn beide batterijen-inbegrepen en leveren het stylingssysteem in dezelfde bundel, waardoor ze standaard in het bereik van circa 180-220 KB gezipt uitkomen.
De afweging is reëel maar kleiner dan het discours suggereert. Een combobox die niet reageert op Home en End kost ongeveer hetzelfde aantal bytes als een die dat wel doet. De keuze is niet “toegankelijkheid versus prestaties” — het is “specificatie-afgeleid gedrag versus handmatig geïmplementeerd gedrag”, en de handmatig geïmplementeerde versie is vaker de grotere, omdat die zijn eigen ad-hoc-toestandsmachine meebrengt.
6. Draaiboek voor het kiezen van een stack
Als het product een enterprise-app is met een aanbestedingsgestuurde toegankelijkheidsvereiste, kies dan React Aria Components.
Het is de enige geauditeerde bibliotheek waarvan de API’s expliciet zijn afgeleid van de WAI-ARIA Authoring Practices Guide. De bundel is zwaarder dan Radix, maar het verhaal voor een VPAT-beoordelaar is het zuiverst omdat elke primitief een gepubliceerde conformiteitsclaim heeft.
Als het team het eigen ontwerpsysteem beheert, kies dan Radix UI (en optioneel shadcn/ui erbovenop).
Radix biedt ongestylede, specificatie-conforme primitieven. shadcn/ui maakt het eenvoudig om ze te kopiëren en te thematiseren. Het aandachtspunt zijn de drie patronen die shadcn niet levert — combobox, listbox, menubalk — waarvoor men Radix rechtstreeks moet importeren in plaats van handmatig te implementeren.
Als het team Tailwind-first is en slechts een beperkt oppervlak nodig heeft, kies dan Headless UI — maar audit de menuprimtief in de eigen code.
Headless UI is de kleinste bundel in het veld en levert een klein, goed getest oppervlak. Het ene menuprimtief-hiaat is hierboven gedocumenteerd; herstel het met een expliciete aria-activedescendant-koppeling op het listbox-stijl menu, of wikkel het menu in een projectlokale helper die dat doet.
Als het team batterijen-inbegrepen boven specificatie-conformiteit verkiest, kies dan Mantine of Chakra v3 — maar plan voor overschrijvingen.
Beide bibliotheken zijn snel om mee te leveren en zien er goed uit uit de doos. Beide vereisen ook per-component-overschrijvingen om de audit te halen op combobox, schakelaar, schuifregelaar (Mantine) of tooltip (Chakra v3). Begreet het overschrijvingswerk in de sprint die de bibliotheek adopteert, niet de sprint die de audit faalt.
Voer axe uit op de eigen demo van de bibliotheek voordat deze wordt geadopteerd.
Leveranciers onderhouden demosites. Richt axe DevTools erop. Als de eigen demo van de leverancier schendingen geeft op de primitieven die men wil gebruiken, zullen die schendingen meegaan naar het product. Deze enkele vijf-minuten-controle scheidt de bibliotheken die men adopteert van de bibliotheken die men vermijdt.
Conclusie: de specificatie is het contract
De bibliotheken die een axe-audit schoon doorstaan, zijn de bibliotheken waarvan de auteurs de WAI-ARIA Authoring Practices Guide als een contract behandelden in plaats van als een referentie. Radix UI, React Aria Components en Ark UI leiden elk hun toetsenbordcontracten en ARIA-koppeling af van een gepubliceerde specificatie — APG in het geval van Radix en React Aria, de Zag.js-toestandsmachines in het geval van Ark UI — en ze leveren de specificatie, niet een deelverzameling ervan.
De bibliotheken die falen, falen niet omdat hun auteurs toegankelijkheid niet belangrijk vinden. Ze falen omdat het toetsenbordcontract werd behandeld als een functieoverzicht, en functieoverzichten eindigen op 80% in plaats van 100%. De laatste 20% — Home en End in een combobox, Escape om een bij hover geopende tooltip te sluiten, focusbeheer bij de eerste ArrowDown in een menu — is het deel dat de gebruiker opmerkt.
Het verhaal van shadcn/ui strekt zich ongemakkelijk over beide categorieën uit. De componenten die het levert, erven Radix’s specificatie-afleiding en slagen. De componenten die het niet levert, zijn de hiaten waar teams een interne combobox handmatig implementeren, en die interne combobox is waar de volgende axe-schending de codebase binnenkomt. De oplossing is niet het kiezen van een andere bibliotheek — het is Radix rechtstreeks importeren voor die drie patronen en ze met dezelfde ernst behandelen als de rest van het ontwerpsysteem.
Engineeringteams die een bibliotheek adopteren, dienen de keuze te combineren met de rest van de ontwikkelaarstoolkit op onze ontwikkelaarstartpagina, een gratis WCAG 2.2-scan uit te voeren op de demo van de bibliotheek voordat deze naar productie wordt uitgerold, en het resultaat te vergelijken met de volledige WCAG 2.2-succescriteria-referentie.
”Kies de bibliotheek waarvan de auteurs de specificatie als een contract behandelden, en de audit wordt een formaliteit. Kies de bibliotheek waarvan de auteurs de specificatie als een referentie behandelden, en de audit wordt een achterstand.”