WCAG 2.2 AA für React: Die Komponenten-Checkliste, die Prüfer wirklich anwenden

#WCAG 2.2 Stufe AA
Sandor Farkas - Founder & Lead Developer at Wolf-Tech

Sandor Farkas

Gründer & Lead Developer

Experte für Softwareentwicklung und Legacy-Code-Optimierung

WCAG 2.2 AA ist leicht behauptet und auf Komponentenebene schwer zu belegen. Automatisierte Scanner wie axe-core, Lighthouse und die Browser-Erweiterung von Deque finden rund 30 bis 40 Prozent der WCAG-Verstöße. Der Rest erfordert manuelle Tests, Tastatur-Durchläufe und das Wissen darum, wo React-Komponentenbibliotheken typischerweise brechen. Diese Checkliste deckt ab, worauf Prüfer tatsächlich achten, wenn sie eine React-Codebasis auf Konformität mit WCAG 2.2 Stufe AA prüfen, einschließlich der Muster, die automatisierte Scans bestehen und ein echtes Accessibility-Review trotzdem nicht.

Was WCAG 2.2 ergänzt hat und React-Teams häufig übersehen

WCAG 2.2 (veröffentlicht im Oktober 2023 und inzwischen Referenzstandard für den European Accessibility Act) hat mehrere neue Erfolgskriterien auf AA-Ebene eingeführt. Drei davon verursachen in React-Komponentenbibliotheken durchgehend Probleme, weil sie Verhalten betreffen, das automatisierte Werkzeuge nicht zuverlässig erkennen können.

2.4.11 Fokus nicht verdeckt (Minimum) verlangt, dass fokussierte Komponenten nicht vollständig von Sticky-Headern, Cookie-Bannern oder schwebenden UI-Elementen verdeckt werden. Eine Komponente kann automatisierte Fokustests bestehen, sie erhält den Fokus, der Fokusring ist sichtbar, und dieses Kriterium trotzdem verfehlen, weil das fokussierte Element in dem Moment unter eine fixierte Navigationsleiste scrollt, in dem der Fokus dorthin wandert. In React-Anwendungen mit Sticky-Layouts ist das häufig genug, dass es nach dem automatisierten Scan als Erstes geprüft werden sollte.

2.5.3 Zielgröße (Minimum) verlangt, dass Ziele für Zeigegeräte mindestens 24 mal 24 CSS-Pixel groß sind, es sei denn, das Ziel liegt inline im Text oder der Abstand zwischen den Zielen liefert die äquivalente Fläche. Buttons mit reinen Icons (Schließen, Aufklappen, Sortier-Spaltenköpfe), Tags und Chip-Komponenten in React-UI-Bibliotheken liegen regelmäßig unter dieser Schwelle. Die Tailwind-Utility p-1 auf einem 16-Pixel-Icon ergibt ein 24-Pixel-Ziel, also exakt die Grenze. Alles Kleinere fällt durch.

3.2.6 Konsistente Hilfe wird seltener durch Komponentencode ausgelöst und häufiger durch Navigationsmuster auf Seitenebene, hat in einem Komponenten-Audit also niedrigere Priorität.

Fokus-Management: die Kategorie, die automatisierte Werkzeuge weitgehend ignorieren

Fehler im Fokus-Management haben in React-Anwendungen die größten Folgen und tauchen am seltensten in automatisierten Scans auf. Clientseitiges Routing, Modal-Muster und bedingtes Rendering erzeugen allesamt Situationen, in denen der Fokus stillschweigend zum Dokument-Body wandert oder ganz verschwindet.

Routenwechsel. Wenn ein Nutzer über einen Next.js-Link oder React Router zwischen Seiten navigiert, wird der neue Seiteninhalt gerendert, der Fokus bleibt aber dort, wo er war, oft auf einem Navigationslink, der in Bezug auf den neuen Inhalt keine sinnvolle Rolle mehr spielt. Die native Seitennavigation des Browsers setzt den Fokus an den Anfang zurück, die virtuelle Navigation von React tut das nicht.

Die übliche Lösung ist ein Hook für das Fokus-Management, der den Fokus nach der Navigation auf die <h1> der Seite oder ein Sprungziel setzt:

// hooks/useFocusOnRouteChange.ts
import { useEffect, useRef } from 'react'
import { usePathname } from 'next/navigation'

export function useFocusOnRouteChange() {
  const pathname = usePathname()
  const mainHeadingRef = useRef<HTMLHeadingElement>(null)

  useEffect(() => {
    mainHeadingRef.current?.focus()
  }, [pathname])

  return mainHeadingRef
}

// In deiner Page-Komponente:
// const headingRef = useFocusOnRouteChange()
// <h1 ref={headingRef} tabIndex={-1}>Seitentitel</h1>

Beachte das tabIndex={-1} an der Überschrift. Ohne dieses Attribut bewirkt focus() bei nicht-interaktiven Elementen in den meisten Browsern nichts. Das Element ist dann programmatisch fokussierbar, aber nicht Teil der Tab-Reihenfolge, und genau das willst du.

Modale Dialoge. Ein Dialog, der sich öffnet, muss den Fokus in sich einschließen (sodass Tab und Shift+Tab nur durch die Dialog-Bedienelemente laufen) und den Fokus beim Schließen an das auslösende Element zurückgeben. Beide Verhaltensweisen müssen manuell getestet werden. Das Fehlermuster ist verbreitet: Der Dialog rendert, der Fokus bleibt auf dem Button, der ihn geöffnet hat (oder landet im Body), und Tab bewegt den Fokus in den Inhalt hinter dem Overlay.

Für eigene Dialoge, die weder das native <dialog>-Element noch eine Bibliothek wie Radix UI nutzen, muss die Fokusfalle die fokussierbaren Nachkommen ermitteln und Keydown-Events abfangen:

function useFocusTrap(ref: React.RefObject<HTMLElement>, active: boolean) {
  useEffect(() => {
    if (!active || !ref.current) return

    const focusable = ref.current.querySelectorAll<HTMLElement>(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    )
    const first = focusable[0]
    const last = focusable[focusable.length - 1]

    first?.focus()

    function handleKeyDown(e: KeyboardEvent) {
      if (e.key !== 'Tab') return
      if (e.shiftKey) {
        if (document.activeElement === first) {
          e.preventDefault()
          last?.focus()
        }
      } else {
        if (document.activeElement === last) {
          e.preventDefault()
          first?.focus()
        }
      }
    }

    document.addEventListener('keydown', handleKeyDown)
    return () => document.removeEventListener('keydown', handleKeyDown)
  }, [active, ref])
}

Wenn deine Komponentenbibliothek auf Radix-UI-Primitives aufsetzt, erledigen Dialog.Root und Dialog.Content die Fokusfalle korrekt. Manuell zu prüfen bleibt die Fokusrückgabe beim Schließen: Radix gibt den Fokus an den Auslöser zurück, wenn der Dialog per Escape geschlossen wird, aber eigene Close-Handler, die onOpenChange von Radix umgehen, tun das womöglich nicht.

Aufklappende und einklappende Inhalte. Akkordeons, Disclosure-Widgets und ausklappbare Tabellenzeilen müssen den Fokus auf dem Bedienelement halten, das den Zustandswechsel ausgelöst hat. Der Fokus sollte nicht in den neu eingeblendeten Inhalt springen (das stört den Lesefluss) und darf keinesfalls ganz verloren gehen.

Sichtbare Fokusindikatoren: mehr als der Standard-Browserring

WCAG 2.2 AA verlangt für fokussierte Komponenten Fokusindikatoren, die ein Mindestkontrastverhältnis und eine Mindestfläche erreichen (2.4.11 und 2.4.13). Das verbreitete Muster, Fokus-Outlines global zu entfernen (*:focus { outline: none }) und auf mausorientierte Designs zu setzen, fällt schlicht durch.

:focus-visible ist die richtige CSS-Pseudoklasse für 2026, sie zeigt Fokusringe nur bei Tastaturnavigation und nicht beim Mausklick. Aber :focus-visible allein genügt nicht, wenn der Fokusindikator zu wenig Kontrast hat oder zu dünn ist.

/* Minimal konformer sichtbarer Fokus - Farben an deine Marke anpassen */
:focus-visible {
  outline: 3px solid #0066cc;
  outline-offset: 2px;
}

/* Für dunkle Hintergründe */
.dark-surface :focus-visible {
  outline-color: #66b3ff;
}

Der Fokusindikator braucht mindestens 3:1 Kontrast gegenüber den angrenzenden Farben, also sowohl gegenüber dem Komponentenhintergrund als auch gegenüber dem umgebenden Seitenhintergrund. Ein weißer Fokusring auf einer hellgrauen Button-Fläche fällt durch. Prüfe Fokusindikatoren über alle Komponentenvarianten hinweg, nicht nur im Standardzustand.

Berechnung des zugänglichen Namens in dynamischen Komponenten

Automatisierte Werkzeuge prüfen, ob interaktive Elemente einen zugänglichen Namen haben. Was sie oft übersehen: ob sich dieser Name korrekt ändert, wenn sich der Komponentenzustand ändert, und ob die berechneten Namen im Kontext überhaupt aussagekräftig sind.

Buttons mit reinen Icons. Ein Button, der nur ein SVG-Icon enthält, braucht einen zugänglichen Namen über aria-label. So weit, so klar. Übersehen wird, dass der Name die Aktion beschreiben muss und nicht das Icon. aria-label="X" ist nicht dasselbe wie aria-label="Dialog schließen".

Umschalt-Buttons. Ein Button, der zwischen zwei Zuständen wechselt, etwa stumm und laut, ein- und ausblenden, auf- und zuklappen, muss den aktuellen Zustand kommunizieren. Dafür gibt es zwei korrekte Muster:

// Muster 1: aria-pressed (der Button ist das Bedienelement)
<button aria-pressed={isMuted} onClick={toggleMute}>
  {isMuted ? <MuteIcon /> : <SpeakerIcon />}
  <span className="sr-only">{isMuted ? 'Ton an' : 'Ton aus'}</span>
</button>

// Muster 2: aria-expanded (der Button steuert eingeblendeten Inhalt)
<button
  aria-expanded={isOpen}
  aria-controls="panel-id"
  onClick={togglePanel}
>
  Details
</button>
<div id="panel-id" hidden={!isOpen}>...</div>

Das Fehlermuster ist ein Umschalt-Button, dessen visuelles Label wechselt (das Icon tauscht), dessen aria-label aber auf einen Zustand festverdrahtet ist. Screenreader-Nutzer hören dann "Ton aus", unabhängig davon, ob der Ton bereits stumm ist.

Select-Komponenten und Comboboxen. Eigene Select-Implementierungen aus <div>- und <ul>-Elementen brauchen ein vollständiges ARIA-Widget-Muster, um bedienbar zu sein. Das ARIA-Combobox-Muster verlangt bestimmte Rollenzuweisungen, Tastaturbehandlung (Pfeiltasten, Pos1/Ende, Escape) und Live-Region-Ansagen, wenn sich der ausgewählte Wert ändert. Das ist komplex genug, dass eine erprobte Bibliothek wie Downshift, Radix Select oder useSelect aus React Aria fast immer die bessere Wahl ist als ein Eigenbau. Wenn du bereits eine eigene Implementierung hast, ist der Abgleich mit der Combobox-Spezifikation aus dem ARIA APG der schnellste Weg, um Lücken zu finden.

Die Fehler, die nur im manuellen Test auftauchen

Mehrere Kategorien von WCAG-2.2-AA-Verstößen sind strukturell nicht automatisiert erkennbar und erfordern Tests von Hand.

Erkennung von Tastaturfallen. Tabbe ausschließlich per Tastatur durch jede interaktive Komponente. Jede Stelle, an der der Fokus gefangen wird und weder mit Tab weiterbewegt noch mit Escape verlassen werden kann, ist ein Verstoß gegen Stufe A. Automatisierte Werkzeuge können dynamische Fallen, die nur in bestimmten UI-Zuständen auftreten, nicht erkennen.

Lesereihenfolge gegen visuelle Reihenfolge. Screenreader folgen der DOM-Reihenfolge, nicht dem visuellen Layout. CSS Grid und Flexbox machen es leicht, Elemente visuell so umzuordnen, dass sie von ihrer DOM-Position abweichen. Ein Layout mit grid-template-areas, das den Sidebar-Inhalt im DOM zuerst, visuell aber zuletzt platziert, führt dazu, dass Tastatur- und Screenreader-Nutzer den Inhalt in einer anderen Reihenfolge antreffen als sehende Nutzer.

Umgang mit Zeitlimits. WCAG 2.2 AA (2.2.1) verlangt, dass Nutzer jedes Zeitlimit abschalten, anpassen oder verlängern können, sofern es nicht länger als 20 Stunden ist oder unter eine Echtzeit-Ausnahme fällt. Session-Timeouts, Countdown-Timer für Autosave und Warnungen vor dem Ablauf von Formularen müssen das Zeitlimit sichtbar machen und einen Weg zur Verlängerung bieten. Automatisierte Werkzeuge können nicht testen, ob deine Timeout-Warnung an Screenreader gemeldet wird oder ob der Verlängerungsmechanismus per Tastatur erreichbar ist.

Fokus durch Sticky-Elemente verdeckt. Wie oben erwähnt: Tabbe manuell durch Seiten mit Sticky-Headern und schwebenden Aktionsbuttons. Vergewissere dich, dass bei keiner Viewport-Breite ein fokussiertes Element vollständig hinter einem fixiert positionierten Element verschwindet.

Ein Komponenten-Audit vor der Frist des European Accessibility Act

Der European Accessibility Act gilt für Produkte und Dienstleistungen, die in der EU angeboten werden, mit Durchsetzung ab Juni 2025. Für SaaS-Produkte mit europäischen Kunden ist die Konformität mit WCAG 2.2 AA keine Best-Practice-Empfehlung mehr, sondern eine rechtliche Grundlinie.

Ein Audit auf Komponentenebene entlang dieser Checkliste dauert je nach Größe der Bibliothek typischerweise ein bis drei Tage und liefert eine nach Schweregrad priorisierte Liste von Befunden. Schwere Befunde (nicht bedienbare interaktive Elemente, fehlende zugängliche Namen, Fokusfallen) blockieren Menschen mit Behinderungen direkt. Mittlere Befunde (zu geringer Kontrast des Fokusindikators, kleinere Lücken im Fokus-Management) verschlechtern die Bedienbarkeit, ohne den Zugang vollständig zu blockieren. Das Auditergebnis gibt Entwicklungsteams ein klares Sanierungs-Backlog statt eines Bestanden-oder-nicht-Stempels, mit dem sich nicht arbeiten lässt.

Wenn dein Team sich auf ein Accessibility-Review vorbereitet oder eine unabhängige Prüfung der eigenen React-Komponentenbibliothek braucht: Wolf-Tech bietet fokussiertes Code-Quality-Consulting, das Barrierefreiheit neben Sicherheit, Performance und Architekturmustern abdeckt. Erreichbar sind wir unter hello@wolf-tech.io oder über wolf-tech.io, wenn du besprechen möchtest, was eine Prüfung deiner konkreten Codebasis abdecken würde.