Experte für Automatisierte Barrierefreiheit (Accessibility Automation Expert)
Implementiere und erzwinge WCAG 2.2 AA/AAA-Konformität durch automatisierte Tests, Tastaturnavigation, ARIA-Muster, Screenreader-Optimierung und Fokusmanagement.
Aktivierungstrigger
Aktivieren bei: Fehlern in Barrierefreiheitsaudits, WCAG-Konformitätsanforderungen, defekter Tastaturnavigation, Screenreader, die Inhalte nicht ansagen, axe-core-Verstößen, Fokusfallen für Modals/Dialoge, Fragen zu aria-*-Attributen, Skip-Navigationslinks.
NICHT für: Designorientierte Barrierefreiheitsprüfung (Farbauswahl, Layoutentscheidungen) – verwende design-accessibility-auditor. Reine Farbkontrastprüfung – verwende css-in-js-architect mit OKLCH.
Schnellstart
- Führe axe-core aus –
npx @axe-core/cli http://localhost:3000oder integriere@axe-core/reactim Dev-Modus für Konsolenwarnungen. - Behebe zuerst kritische Verstöße – fehlender Alternativtext, fehlende Formularbeschriftungen, unzureichender Kontrast, fehlende Landmarks.
- Implementiere Tastaturnavigation – jedes interaktive Element muss mit Tab, Eingabetaste, Leertaste, Escape und Pfeiltasten erreichbar und bedienbar sein.
- Füge Fokusmanagement hinzu – Fokus in Modals einfangen, Fokus beim Schließen wiederherstellen, Fokus bei Routenwechsel verwalten.
- Teste mit einem Screenreader – VoiceOver (macOS), NVDA (Windows) oder TalkBack (Android) für echte Validierung.
Kernfunktionen
| Bereich | Technologien | Schlüsselmuster |
|---|---|---|
| Automatisierte Tests | axe-core, Lighthouse, jest-axe, Playwright axe | CI/CD-Barrierfreiheitsgates |
| Tastaturnavigation | tabindex, onKeyDown, roving tabindex | Pfeiltastennavigation, Fokusgruppen |
| Screenreader | ARIA-Rollen, Live-Regionen, aria-label | Ansagen, Zustandsänderungen, Beschreibungen |
| Fokusmanagement | focus-visible, Fokusfalle, inert-Attribut | Modalfokussperre, Skip-Links, Fokus bei Routenwechsel |
| Semantisches HTML | <main>, <nav>, <article>, <aside> | Landmarks, Überschriftenhierarchie, Listen |
| Formulare | <label>, aria-describedby, aria-invalid | Fehleransage, Pflichtfelder, Fieldsets |
Architekturmuster
Muster 1: Automatisierte axe-core-Prüfung in CI
// e2e/accessibility.spec.ts (Playwright + axe-core)
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
const pages = ['/', '/products', '/checkout', '/account'];
for (const path of pages) {
test(`${path} hat keine Barrierefreiheitsverstöße`, async ({ page }) => {
await page.goto(path);
await page.waitForLoadState('networkidle');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag22aa']) // WCAG 2.2 AA
.analyze();
expect(results.violations).toEqual([]);
});
}
// Dev-Modus: axe-core in React (zeigt Verstöße in der Konsole)
// app/layout.tsx
if (process.env.NODE_ENV === 'development') {
import('@axe-core/react').then((axe) => {
axe.default(React, ReactDOM, 1000);
});
}
Muster 2: Fokusmanagement für Modals
import { useEffect, useRef, useCallback } from 'react';
function useFocusTrap(isOpen: boolean) {
const containerRef = useRef<HTMLDivElement>(null);
const previousFocusRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!isOpen) return;
// Aktuellen Fokus speichern, um ihn später wiederherzustellen
previousFocusRef.current = document.activeElement as HTMLElement;
// Erstes fokussierbares Element fokussieren
const container = containerRef.current;
if (!container) return;
const focusable = container.querySelectorAll<HTMLElement>(
'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
focusable[0]?.focus();
// Fokus innerhalb des Containers einfangen
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'Tab') return;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last?.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first?.focus();
}
};
container.addEventListener('keydown', handleKeyDown);
// inert auf Hintergrundinhalte anwenden
const mainContent = document.querySelector('main');
mainContent?.setAttribute('inert', '');
return () => {
container.removeEventListener('keydown', handleKeyDown);
mainContent?.removeAttribute('inert');
previousFocusRef.current?.focus(); // Fokus wiederherstellen
};
}, [isOpen]);
return containerRef;
}
Muster 3: ARIA-Live-Regionen für dynamische Inhalte
// Formularfehler, Ladezustände und Updates für Screenreader ansagen
function useAnnounce() {
const announce = useCallback((message: string, priority: 'polite' | 'assertive' = 'polite') => {
const el = document.getElementById(`aria-live-${priority}`);
if (el) {
el.textContent = ''; // Zuerst leeren, um erneute Ansage auszulösen
requestAnimationFrame(() => { el.textContent = message; });
}
}, []);
return announce;
}
// Einmal im Layout einbinden:
function AriaLiveRegions() {
return (
<>
<div id="aria-live-polite" aria-live="polite" aria-atomic="true" className="sr-only" />
<div id="aria-live-assertive" aria-live="assertive" aria-atomic="true" className="sr-only" />
</>
);
}
┌─ Pyramide für Barrierefreiheitstests ───────────────┐
│ │
│ ▲ Manuelle Screenreader-Tests │
│ ╱ ╲ (VoiceOver, NVDA — vierteljährlich) │
│ ╱───╲ │
│ ╱ ╲ Playwright + axe-core E2E │
│ ╱ E2E ╲ (jede Seite, CI-Gate) │
│ ╱─────────╲ │
│ ╱ ╲ jest-axe-Komponententests │
│ ╱ Komponente ╲ (pro interaktive Komponente) │
│ ╱───────────────╲ │
│ ╱ ╲ ESLint jsx-a11y │
│╱ Statischer Lint╲ (bei jedem Commit) │
│╲___________________╱ │
└─────────────────────────────────────────────────────┘
Anti-Patterns
divmitonClickstattbutton– Divs haben keine Tastaturinteraktion, keine Rolle und keinen Fokus. Verwende semantische<button>- oder<a>-Elemente. Wenn du eindivverwenden musst, fügerole="button",tabindex="0"undonKeyDownfür Eingabetaste/Leertaste hinzu.aria-labelauf allem – Überbeschriftung erzeugt Rauschen für Screenreader-Nutzer. Bevorzuge sichtbare Textbeschriftungen; verwendearia-labelnur, wenn kein sichtbarer Text möglich ist.- Fokusumrandungen ausblenden –
outline: noneohne Alternative macht die Tastaturnavigation unsichtbar. Verwende:focus-visiblefür tastatur-only Fokusindikatoren, die nicht bei Mausklicks erscheinen. - Fehlender Skip-Navigationslink – Tastaturnutzer müssen bei jeder Seite die gesamte Navigation durchtabben. Füge
<a href="#main" class="sr-only focus:not-sr-only">Zum Hauptinhalt springen</a>als erstes fokussierbares Element hinzu. - Toast-Benachrichtigungen ohne Live-Regionen – Screenreader können dynamisch hinzugefügte Toasts nicht erkennen. Verwende
aria-live="polite"-Regionen, um sie anzukündigen.
Qualitäts-Checkliste
- axe-core CI-Gate besteht ohne Verstöße auf allen Seiten
-
eslint-plugin-jsx-a11yaktiviert ohne Warnungen - Alle Bilder haben beschreibenden
alt-Text (oderalt=""für dekorative Bilder) - Alle Formulareingaben haben zugehörige
<label>-Elemente (nicht nur Platzhaltertext) - Überschriftenhierarchie ist sequenziell (
h1>h2>h3, keine Sprünge) - Seiten-Landmarks vorhanden:
<main>,<nav>,<header>,<footer> - Skip-Navigationslink ist das erste fokussierbare Element
- Fokus sichtbar auf allen interaktiven Elementen (
:focus-visible-Stile definiert) - Modals fangen den Fokus ein und stellen ihn beim Schließen wieder her
- Dynamische Inhaltsänderungen werden über
aria-live-Regionen angekündigt - Farbkontrastverhältnisse erfüllen WCAG AA (4,5:1 normaler Text, 3:1 großer Text)
- Alle Funktionen nur mit Tastatur bedienbar (Tab, Eingabetaste, Leertaste, Escape, Pfeiltasten)