Blog/Web-Engineering
Barrierefreie Vue-Komponenten: die WCAG-2.2-Muster, die automatisierte Tests übersehen
WCAG-2.2-Muster auf Komponentenebene für Vue und Nuxt: Fokus in Modals und Teleport, Live-Regionen, Comboboxen, Lücken von axe-core und eine Teststufenleiter.
Balázs Csorba··12 Min. Lesezeit
- Vue accessibility
- WCAG 2.2
- Focus management
- Live regions
- Component testing

Das Wichtigste in Kürze
- Fünf der neun neuen Kriterien von WCAG 2.2 betreffen den Komponentencode direkt: Fokus nicht verdeckt, Ziehbewegungen, Zielgröße, redundante Eingaben und zugängliche Authentifizierung.
- axe-core findet im Schnitt 57 % der WCAG-Probleme automatisch, und seine einzige Regel für WCAG 2.2 ist standardmäßig deaktiviert; ein grüner Scan belegt also keine Qualität für Tastatur oder Screenreader.
- Die Fokusverwaltung gehört in die Komponente: Ein natives Dialogelement mit inertem Hintergrund ist die günstigere Basis, und Routenwechsel in Single-Page-Anwendungen brauchen eine ausdrückliche Fokusverschiebung.
- Live-Regionen müssen existieren, bevor sie sich ändern, und nur Klartext enthalten; gestreamte KI-Ausgaben sollten in satzgroßen Stücken angesagt werden statt Token für Token.
- Testen Sie in einer Leiter von Lint und Komponenten-Scans bis zu ARIA-Snapshots und einer manuellen Screenreader-Prüfung, und schreiben Sie die Definition of Done auf, damit das Review sie durchsetzen kann.
Die meisten Barrierefreiheitsfehler in einer Vue-App sind Komponentenfehler. Ein Dialog, der den Fokus nicht zurückgibt, ein Toast, den ein Screenreader nie ansagt, eine Combobox, die auf die Pfeiltasten nicht reagiert, ein Symbolbutton mit 16 Pixeln Kantenlänge: Jeder dieser Fehler wird einmal in einer gemeinsamen Komponente geschrieben und dann auf jeder Seite ausgeliefert, die sie verwendet. Deshalb sind Komponenten die richtige Einheit für Barrierefreiheit und der richtige Ort, um die Regeln festzuhalten.
Dieser Artikel ist die technische Hälfte von WCAG 2.2 für Vue und Nuxt. Er ordnet die Erfolgskriterien zu, die in Komponentencode landen, erklärt, was axe-core sehen kann und was nicht, behandelt Fokusverwaltung, asynchrone Live-Regionen und eigene Comboboxen und endet mit einer Teststufenleiter und einer Definition of Done, die Sie in eine Vorlage für Pull Requests übernehmen können. Ob eine Website konform ist, entscheidet er nicht; den rechtlichen Rahmen beschreibt der European Accessibility Act für B2B-Shops.
Welche WCAG-2.2-Kriterien betreffen den Komponentencode?
Fünf der neun Kriterien, die WCAG 2.2 als neu kennzeichnet, betreffen den Komponentencode direkt: 2.4.11 Fokus nicht verdeckt, 2.5.7 Ziehbewegungen, 2.5.8 Zielgröße (Minimum), 3.3.7 Redundante Eingaben und 3.3.8 Zugängliche Authentifizierung (Minimum). Die älteren Kriterien, die am häufigsten scheitern, bleiben 2.4.7 Fokus sichtbar sowie die Kontrastkriterien 1.4.3 und 1.4.11. Sie haben sich in Version 2.2 nicht geändert, stecken aber in jedem eigenen Design.
Jedes Kriterium entspricht einem bestimmten Vue-Artefakt. Fokus nicht verdeckt bedeutet meist, dass ein fixierter Header, eine Cookie-Leiste oder ein schwebendes Chat-Widget die fokussierte Zeile oder Schaltfläche überdeckt. Die Zielgröße betrifft reine Symbol-Schließen-Buttons und Werkzeugleisten, deren Trefferfläche kleiner als 24 × 24 CSS-Pixel ist. Ziehbewegungen betreffen sortierbare Listen und Schieberegler, die eine Alternative mit einem einzelnen Zeiger brauchen, etwa Schaltflächen. Redundante Eingaben treffen mehrstufige Formulare, die eine Adresse erneut abfragen, die der Nutzer zwei Schritte zuvor schon eingegeben hat. Zugängliche Authentifizierung betrifft Anmeldemasken und Einmalcode-Felder, die Einfügen oder Passwortmanager blockieren.
Die Tabelle ist die Arbeitskarte: eine Zeile je Kriterium, das Vue-Artefakt, in dem es auftaucht, ob axe-core dafür heute eine Regel hat, und die günstigste Korrektur.
| Kriterium | Vue-Artefakt | axe-Regel heute | Günstigste Korrektur |
|---|---|---|---|
| 2.4.7 Fokus sichtbar (AA) | Fokusstile eigener Bedienelemente | keine | nativen Rahmen behalten; ersetzen, nie nur entfernen |
| 2.4.11 Fokus nicht verdeckt (AA) | fixierter Header, Cookie-Leiste, Chat-Widget | keine | scroll-margin für Fokusziele |
| 2.5.7 Ziehbewegungen (AA) | sortierbare Listen, Schieberegler | keine | Schaltflächen, die Elemente verschieben |
| 2.5.8 Zielgröße (AA) | reine Symbol-Schließen- und Werkzeugleisten-Buttons | target-size, standardmäßig aus | Trefferfläche von 24 × 24 px |
| 3.3.7 Redundante Eingaben (A) | mehrstufige Checkout- und Adressformulare | keine | aus früheren Schritten vorbefüllen |
| 3.3.8 Zugängliche Authentifizierung (AA) | Anmelde-, OTP- und Passwortfelder | keine | Einfügen und Passwortmanager erlauben |
| 1.4.3 und 1.4.11 Kontrast | Design-Tokens, Rahmen, Fokusrahmen | nur Text (color-contrast) | jedes Token-Paar in jedem Zustand prüfen |
| 4.1.2 Name, Rolle, Wert | eigene Comboboxen, Symbol-Buttons | teilweise | zuerst das native Element verwenden |
Was deckt axe-core tatsächlich ab?
Automatisierte Scanner finden einen brauchbaren Teil der WCAG-Probleme, aber selten die, die einen Tastatur- oder Screenreader-Nutzer typischerweise ausbremsen. axe-core gibt an, im Schnitt 57 % der WCAG-Probleme automatisch zu finden. Der WCAG-2.2-Abschnitt enthält eine einzige Regel, target-size, und die ist standardmäßig deaktiviert.
Die README von axe-core nennt die Obergrenze deutlich: „With axe-core, you can find on average 57% of WCAG issues automatically.“ Die Regelbeschreibungen bestätigen die Lücke bei WCAG 2.2: Die einzige 2.2-Regel ist target-size, deaktiviert, bis WCAG 2.2 breiter verbreitet ist. Keine Regel prüft Fokus nicht verdeckt, redundante Eingaben oder zugängliche Authentifizierung.
Die Bevölkerungsdaten zeigen, dass die Lücke messbar ist. Der Million-Report von WebAIM 2026 hat eine Million Startseiten untersucht und im Schnitt 56,1 erkannte Fehler pro Seite gefunden, 10,1 % mehr als 2025 mit 51. Startseiten mit Vue.js kamen auf durchschnittlich 64,6, Nuxt.js-Seiten auf 54,7 erkannte Fehler. WebAIM stellt klar, dass nicht alle Konformitätsverstöße automatisch erkannt werden können; diese Werte zeigen also, was die Werkzeuge sehen, und sind kein Urteil über eine einzelne Website.
Wie sich der Fokus bei Modals, Teleport und Routenwechseln verhalten sollte
Ein Modal muss den Fokus beim Öffnen hineinholen, Tab und Umschalt+Tab darin halten, mit Escape schließen und den Fokus zu dem Element zurückgeben, das es geöffnet hat. Eine selbst gebaute Fokusfalle kann alle vier Aufgaben erfüllen, aber ein natives Dialogelement, dessen restliche Seite inert markiert ist, erledigt den größten Teil mit weniger Code.
Selbst gebaute Fallen scheitern meist an drei Stellen. Sie sammeln fokussierbare Elemente einmal beim Mounten, daher wird ein später gerendertes Feld übersprungen. Sie begrenzen sich auf den Container, in dem die Komponente geschrieben wurde, aber Teleport verschiebt den DOM-Knoten des Dialogs an eine andere Stelle im Dokument, also verfehlt die Falle ihn. Und sie geben den Fokus an einen Button zurück, den ein erneutes Rendern bereits entfernt hat, sodass der Fokus auf body zurückfällt.
Die günstigere Basis ist das native Dialogelement. Ein Aufruf von showModal() setzt das Element in die Top Layer, macht den Rest des Dokuments inert und schließt mit Escape. Aktuelle Engines geben den Fokus beim Schließen ebenfalls an den Auslöser zurück, doch die Details der Rückgabe unterscheiden sich zwischen den Implementierungen; schreiben Sie dafür also einen Test in jedem Browser, den Sie ausliefern. Wo ein natives Dialogelement nicht passt, erzielt das Attribut inert denselben Effekt auf der Seite hinter einem eigenen Overlay.
Routenwechsel in Vue Router und Nuxt
Eine Navigation in einer Single-Page-Anwendung lädt das Dokument nicht neu; der Browser verschiebt den Fokus also nicht und kündigt die neue Seite nicht an. Der Barrierefreiheitsleitfaden von Vue behandelt das mit einem Skip-Link als erstem fokussierbaren Element und einem Watcher auf route.path, der den Fokus nach jedem Routenwechsel verschiebt. Das hier verwendete Sentinel-Muster legt eine fokussierbare Überschrift an den Anfang des Hauptbereichs, die das Layout nach der Navigation fokussiert. Ein Fokus auf Überschrift oder Sentinel funktioniert besser als ein Fokus auf body, denn body ist für einen Screenreader kein sinnvoller Startpunkt.
<!-- Layout sketch: move focus to the page heading after each route change -->
<script setup lang="ts">
const route = useRoute()
const sentinel = ref<HTMLElement | null>(null)
watch(() => route.path, async () => {
await nextTick() // let the new page render first
sentinel.value?.focus()
})
</script>
<template>
<main>
<h1 ref="sentinel" tabindex="-1"><slot name="title" /></h1>
<slot />
</main>
</template>Kombinieren Sie die Fokusverschiebung nur dann mit einer Ansage, wenn die fokussierte Überschrift dem Nutzer nicht ohnehin zeigt, wo er ist. Ob ein geänderter Dokumenttitel allein angesagt wird, hängt von Screenreader und Browser ab; testen Sie diese Kombination, statt sie vorauszusetzen.
Wie verhalten sich asynchrone Updates und Live-Regionen?
Eine Live-Region kündigt eine Änderung nur an, wenn sie vor der Änderung schon existierte. Rendern Sie den Container mit der Seite und aktualisieren Sie danach seinen Text. Verwenden Sie standardmäßig polite, assertive nur für Fehler, die den Nutzer blockieren, und legen Sie nie Bedienelemente in die Region, denn Live-Regionen werden als Klartext angesagt.
Die MDN-Anleitung zu Live-Regionen läuft auf dieselben Regeln hinaus: die Region zuerst etablieren, die erste inhaltliche Aktualisierung auf einen späteren Task verschieben und assertive nur für dringende Meldungen verwenden. Sie warnt außerdem, dass Live-Regionen in der Regel als reiner Text angesagt werden, sodass Links, Schaltflächen und andere Semantik im aktualisierten Inhalt womöglich nicht übermittelt werden.
Hier bricht die Fehlermeldung mit Toasts. Mountet eine Komponente den Toast zusammen mit seiner Live-Region, erscheinen Region und Meldung im selben DOM-Update, und viele Screenreader sagen die Meldung dann nicht an. Die Lösung ist eine Region, die mit der Seite gerendert wird, vorhanden, aber leer, und in die die Komponente den Toast-Text schreibt. Dasselbe gilt für Zusammenfassungen der Eingabeprüfung und für Speicherbestätigungen nach einem Routenwechsel.
Gestreamte Ausgaben sind der schwierigste Fall. Schreibt jeder Chunk einer LLM-Antwort in eine höfliche Region, liest der Screenreader die Antwort Wort für Wort vor und unterbricht sich ständig. Halten Sie den sichtbaren Text während des Streamings aktuell, und kündigen Sie eine Zusammenfassung oder die fertige Antwort an Satzgrenzen oder am Ende an. Der Beitrag zur Barrierefreiheit von Streaming-Chats behandelt die chatspezifischen Details.
Sollten Sie eine eigene Auswahl oder Combobox bauen?
Verwenden Sie ein natives select, wann immer das Design es erlaubt. Bauen Sie eine eigene Combobox nur, wenn das Produkt etwas braucht, das ein natives Element nicht kann, und besitzen Sie dann den vollständigen Tastaturvertrag aus dem WAI-ARIA Authoring Practices Guide (APG).
Die APG benennt den Preis klar: „Unlike native HTML form elements, browsers do not provide keyboard support for graphical user interface (GUI) components that are made accessible with ARIA; authors have to provide the keyboard support in their code.“ Bei einer Combobox heißt das: Pfeiltasten bewegen sich durch die Optionen, Enter wählt aus, Escape schließt, Tippen filtert die Liste, und der Fokus bleibt dort, wo das Muster ihn vorsieht. Die APG führt Combobox, Listbox, Dialog, Grid, Karussell und Akkordeon als eigene Muster mit eigenen Verträgen, und Teams verwechseln sie oft.
Zwei Umsetzungsentscheidungen sind wichtig. aria-activedescendant lässt den DOM-Fokus im Texteingabefeld und verweist per id auf die markierte Option; das passt zu filterbaren Comboboxen. Ein Roving Tabindex verschiebt den echten Fokus zwischen den Elementen, das passt zu Werkzeugleisten, Menüs und Grids. Entscheiden Sie nach dem, worin der Nutzer tippt: Ein Textfeld spricht für activedescendant, eine Reihe von Bedienelementen für ein Roving Tabindex.
Eine eigene Steuerung ist dauerhafte Wartung. Jedes Framework-Upgrade, jeder neue Optionstyp und jede Designänderung kann den Vertrag brechen, und der Komponententest muss die Tastatur durchspielen, nicht nur das gerenderte Markup. Erfüllt ein natives select mit gestylter Hülle die Anforderung, ist das fast immer die günstigere Komponente.
Was lassen headless-Bibliotheken trotzdem bei Ihnen?
Headless-Komponentenbibliotheken liefern die ARIA-Verdrahtung, die Tastaturbelegung und den Fokusbereich eines Musters. Sie liefern nicht Ihre zugänglichen Namen, Ihren Farbkontrast, Ihre Fehlermeldungen oder die Zielgröße Ihrer Symbolbuttons. Reka UI ist im Oktober 2026 eine der Vue-Optionen, und ihre Dokumentation sagt deutlich, was sie abdeckt.
Reka UI bezeichnet sich als v2-Weiterentwicklung von Radix Vue und sagt, seine Komponenten folgen den WAI-ARIA-Designmustern und übernehmen Details wie aria-Attribute, Tastaturnavigation und Fokusverwaltung. Den Rest des Vertrags behält das Team, dem das Produkt gehört. Die Liste unten ist das, was bei jedem headless Aufbau bei Ihnen bleibt:
- Zugängliche Namen: Ein Symbolbutton, der den Warenkorb schließt, braucht einen Namen wie „Warenkorb schließen“; eine Bibliothek kann nicht erraten, was ein nacktes Symbol bedeutet.
- Kontrast im eigenen Design: Die Bibliothek liefert keine Farben; jedes Token-Paar muss in Hell, Dunkel und jedem Zustand geprüft werden.
- Fehlermeldungen: Der Fehlertext muss über
aria-describedbymit seinem Feld verbunden sein und sagen, was als Nächstes zu tun ist. - Ansagen bei Routenwechseln: Seitenwechsel in einer Single-Page-Anwendung liegen bei Ihnen, wie oben beschrieben.
- Zielgröße: Ein headless Button hat die Größe, die Ihr CSS ihm gibt.
Wie testen Sie das alles?
Testen Sie in Stufen, die günstigste zuerst: Templates linten, jede Komponente in Unit-Tests scannen, echte Abläufe Ende zu Ende scannen, den Barrierefreiheitsbaum mit ARIA-Snapshots prüfen und mit einer manuellen Prüfung per Tastatur und Screenreader abschließen. Jede Stufe findet Fehlerklassen, die die Stufen davor nicht finden, und die oberste Stufe braucht weiterhin einen Menschen.
Lint kommt zuerst, weil die günstigsten Fehler die sind, die ein Template sichtbar macht. eslint-plugin-vuejs-accessibility markiert typische Template-Fehler wie fehlende Alternativtexte und ungültige ARIA-Attribute. Ob ein Alternativtext sinnvoll ist, kann das Plugin nicht beurteilen; es ist ein Filter, kein Urteil.
Unit-Tests führen axe-core für jede Komponente isoliert aus, sodass ein Fehler auf eine Komponente zeigt.
// Vitest: scan the rendered dialog, not the whole page
import axe from 'axe-core'
it('confirm dialog has no detectable violations', async () => {
const { container } = render(ConfirmDialog, { props: { open: true } })
const results = await axe.run(container)
expect(results.violations).toEqual([])
})Die Ende-zu-Ende-Stufe führt @axe-core/playwright über echte Abläufe aus: Dialog öffnen, Formular mit einem Fehler absenden, dann den Zustand scannen, den der Nutzer tatsächlich erreicht. Der Playwright-Leitfaden zur Barrierefreiheit empfiehlt diese Kombination und sagt deutlich, dass viele Probleme nur manuell zu finden sind. Playwright hat außerdem toMatchAriaSnapshot seit Version 1.49, das den Barrierefreiheitsbaum prüft; eine Schaltfläche, die still ihren Namen verliert, lässt einen Test scheitern, den ein visueller Snapshot übersähe.
// Playwright: scan the state a user reaches, not the empty page
import AxeBuilder from '@axe-core/playwright'
await page.getByRole('button', { name: 'Delete order' }).click()
const results = await new AxeBuilder({ page }).include('[role="dialog"]').analyze()
expect(results.violations).toEqual([])Die oberste Stufe ist ein Mensch mit Screenreader. Die WebAIM Screen Reader User Survey #11, erhoben im Juli und August 2026 mit 1.780 Antworten, nennt JAWS als primären Desktop-Screenreader für 55,0 % der Befragten, NVDA für 32,9 % und VoiceOver für 6,5 %. Testen Sie als Basis mit NVDA unter Windows und VoiceOver unter macOS und iOS, und nehmen Sie JAWS hinzu, wenn Ihre Nutzer es verwenden. Gehen Sie zuerst den ganzen Ablauf nur mit der Tastatur durch, denn die meisten Fokusfehler zeigen sich, bevor ein Screenreader läuft. Den zweiten Durchgang führe ich mit dem Playwright-MCP-Server aus einer Agentensitzung aus, sodass der Agent den Dialog öffnet, durchtabt und den Scan startet; die Ansagen hört aber weiterhin ein Mensch.
Für die Entwicklung läuft @nuxt/a11y axe-core in den Nuxt DevTools mit routenbewusstem Scannen und kann Berichte beim nuxt generate erzeugen. Die neueste Version ist noch 1.0.0-alpha.1; nutzen Sie es daher als Entwicklungshilfe und halten Sie das CI-Gate in den oben genannten Stufen. Dieselbe Disziplin gilt wie bei Nuxt Core Web Vitals und Performance-Budgets: Eine Zahl, die in der CI geprüft wird, bleibt wahr; eine Zahl, die einmal geprüft wird, nicht.
Was gehört zur Definition of Done einer Komponente?
Eine Komponente ist fertig, wenn Name, Rolle, Tastaturverhalten, Fokusbehandlung, Kontrast, Zielgröße und Verhalten der Live-Region je geprüft und im Review festgehalten sind. Die Liste unten ist so geschrieben, dass sie in eine Vorlage für Pull Requests oder in einen Review-Skill passt, und jeder Punkt entspricht einer Stufe der Leiter.
- Name und Rolle: Jedes Bedienelement hat einen zugänglichen Namen, und seine Rolle passt zu einem nativen Element oder zum APG-Muster, das es umsetzt.
- Tastatur: Jede Aktion funktioniert mit den Tasten, die das Muster vorsieht; außerhalb von Dialogen gibt es keine Fokusfalle.
- Fokus: Der Fokusindikator ist in hellen und dunklen Themes sichtbar, wird an seiner Scrollposition nicht von fixierten Elementen verdeckt und kehrt nach dem Schließen oder Navigieren zum Auslöser zurück.
- Kontrast: Text erreicht 4,5:1, erforderliche visuelle Zustände erreichen 3:1, in hellen und dunklen Themes.
- Zielgröße: Zeigerziele sind mindestens 24 × 24 CSS-Pixel groß oder so weit auseinander, dass eine Ausnahme greift.
- Live-Regionen: Die Region existiert vor der Änderung, ist höflich, außer die Meldung blockiert den Nutzer, und enthält nur Klartext.
- Formulare: Fehler sind ihren Feldern zugeordnet und hängen nicht allein an der Farbe, redundante Eingaben sind vorbefüllt, und Authentifizierungsfelder erlauben Einfügen und Passwortmanager.
- Ziehen: Jede Zieh-Aktion hat eine Alternative mit einem einzelnen Zeiger.
- Bewegung: Übergänge mit Bedeutung respektieren
prefers-reduced-motion. - Rechts-nach-links: Fokusindikator und Symbolrichtung funktionieren in rechts-nach-links-Layouts.
Sobald die Liste festgehalten ist, kann ein Review-Agent neue Komponenten dagegen prüfen, bevor ein Mensch den Diff liest; die Screenreader-Prüfung bleibt bei einem Menschen. Ich pflege etwa 20 Agenten-Skills in ~/.agents/skills, und diese Checkliste ist ein naheliegender Kandidat dafür. Wenn Sie eine Vue- oder Nuxt-Komponentenbibliothek bauen oder prüfen und dies in die CI einbinden möchten, sehen Sie sich meine Vue- und Nuxt-Arbeit an.
Quellen
- W3C: Web Content Accessibility Guidelines (WCAG) 2.2
- axe-core README (Deque)
- axe-core rule descriptions
- WebAIM: The WebAIM Million 2026
- WebAIM: Screen Reader User Survey #11
- Vue: accessibility guide
- WAI-ARIA Authoring Practices Guide
- MDN: ARIA live regions
- Playwright: accessibility testing
- Playwright: locator assertions (toMatchAriaSnapshot)
- nuxt/a11y on GitHub
- Reka UI: introduction
Häufige Fragen
Macht axe-core eine Vue-App barrierefrei?
Nein. axe-core findet nach eigenen Angaben im Schnitt 57 % der WCAG-Probleme automatisch, und die WCAG-2.2-Regel ist standardmäßig deaktiviert. Tastatur-, Fokus- und Screenreader-Verhalten muss weiterhin manuell geprüft werden.
Welche WCAG-2.2-Kriterien betreffen Vue-Komponenten am stärksten?
Fokus nicht verdeckt, Ziehbewegungen, Zielgröße (Minimum), redundante Eingaben und zugängliche Authentifizierung (Minimum) betreffen den Komponentencode direkt. Fokus sichtbar und die Kontrastkriterien sind älter, scheitern bei eigenem Styling aber genauso häufig.
Wie behandle ich den Fokus in einem Modal, das Teleport verwendet?
Verwenden Sie ein natives Dialogelement, das mit showModal() geöffnet wird, setzen Sie den Hintergrund bei Bedarf auf inert und testen Sie die Fokusrückgabe nach dem Schließen. Eine selbst gebaute Falle, die innerhalb ihres ursprünglichen Elternelements sucht, übersieht den teleportierten Dialog.
Warum werden meine Ansagen des Screenreaders nicht vorgelesen?
Oft wurde die Live-Region im selben Update erzeugt wie ihre Meldung. Rendern Sie die Region zuerst mit der Seite, lassen Sie sie vorhanden, aber leer, und schreiben Sie die Meldung danach hinein.
Sollte ich eine eigene Combobox oder ein natives select verwenden?
Nehmen Sie ein natives select, außer das Produkt braucht Verhalten, das es nicht bieten kann. Eine eigene Combobox bedeutet, den APG-Tastaturvertrag zu besitzen und ihn bei jedem Upgrade funktionsfähig zu halten.