Barrierefreiheit (a11y)
Umfassende Richtlinien zur Barrierefreiheit basierend auf WCAG 2.2 und Lighthouse-Accessibility-Audits. Ziel: Inhalte für alle nutzbar machen, einschließlich Menschen mit Behinderungen.
Evidenzbasierter Prüfworkflow
Wenn eine gerenderte Seite verfügbar ist:
- Führe einen Live-Lighthouse-Accessibility-Audit durch, wenn diese Funktion verfügbar ist; mit Chrome DevTools MCP verwende
lighthouse_audit. Verwende den mobilen Navigationsmodus für eine öffentliche Seite oder den Snapshot-Modus, wenn ein Neuladen den authentifizierten oder vom Benutzer erstellten Zustand verlieren würde. - Verwende fehlgeschlagene Audit-Knoten, um die relevante Komponente oder Vorlage zu lokalisieren, anstatt das gesamte Repository nach generischen Mustern zu durchsuchen.
- Untersuche einen gerenderten Snapshot des Accessibility-Trees auf Namen, Rollen, Zustände, Orientierungspunkte und Überschriftenstruktur; mit Chrome DevTools MCP verwende
take_snapshot. Teste den betroffenen Ablauf mit der Tastatur. - Korrigiere die Quelle und führe dann dasselbe Audit und die manuelle Interaktion erneut durch.
Falls die Live-Tools nicht verfügbar sind, verwende Lighthouse CLI oder axe für die automatisierte Abdeckung und führe dieselben manuellen Prüfungen durch. Automatisierte Tools erkennen nur einen Teil der Barrieren: Eine Punktzahl von 100 ist keine WCAG-Konformität, und eine niedrige Punktzahl ersetzt keine nachweislichen Probleme auf Einzelfallebene.
WCAG-Prinzipien: POUR
| Prinzip | Beschreibung |
|---|---|
| Wahrnehmbar | Inhalte können durch verschiedene Sinne wahrgenommen werden |
| Bedienbar | Die Benutzeroberfläche kann von allen Benutzern bedient werden |
| Verständlich | Inhalte und Benutzeroberfläche sind verständlich |
| Robust | Inhalte funktionieren mit assistiven Technologien |
Konformitätsstufen
| Stufe | Anforderung | Ziel |
|---|---|---|
| A | Mindestanforderung an Barrierefreiheit | Muss bestanden werden |
| AA | Standardkonformität | Sollte bestanden werden (rechtliche Anforderung in vielen Ländern) |
| AAA | Erweiterte Barrierefreiheit | Wünschenswert |
Wahrnehmbar
Textalternativen (1.1)
Bilder benötigen Alt-Text:
<!-- ❌ Fehlender Alt-Text -->
<img src="chart.png">
<!-- ✅ Beschreibender Alt-Text -->
<img src="chart.png" alt="Balkendiagramm, das einen Anstieg der Q3-Umsätze um 40 % zeigt">
<!-- ✅ Dekoratives Bild (leerer Alt-Text) -->
<img src="decorative-border.png" alt="" role="presentation">
<!-- ✅ Komplexes Bild mit längerer Beschreibung -->
<figure>
<img src="infographic.png" alt="Infografik zu Markttrends 2024"
aria-describedby="infographic-desc">
<figcaption id="infographic-desc">
<!-- Detaillierte Beschreibung -->
</figcaption>
</figure>
Symbol-Buttons benötigen zugängliche Namen:
<!-- ❌ Kein zugänglicher Name -->
<button><svg><!-- Menü-Symbol --></svg></button>
<!-- ✅ Mit aria-label -->
<button aria-label="Menü öffnen">
<svg aria-hidden="true"><!-- Menü-Symbol --></svg>
</button>
<!-- ✅ Mit visuell verstecktem Text -->
<button>
<svg aria-hidden="true"><!-- Menü-Symbol --></svg>
<span class="visually-hidden">Menü öffnen</span>
</button>
Visuell versteckte Klasse:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Farbkontrast (1.4.3, 1.4.6)
| Textgröße | AA-Mindestwert | AAA-verbessert |
|---|---|---|
| Normaler Text (< 18px / < 14px fett) | 4,5:1 | 7:1 |
| Großer Text (≥ 18px / ≥ 14px fett) | 3:1 | 4,5:1 |
| UI-Komponenten & Grafiken | 3:1 | 3:1 |
/* ❌ Niedriger Kontrast (2,5:1) */
.low-contrast {
color: #999;
background: #fff;
}
/* ✅ Ausreichender Kontrast (7:1) */
.high-contrast {
color: #333;
background: #fff;
}
/* ✅ Fokus-Zustände benötigen ebenfalls Kontrast (3:1 zum Hintergrund, WCAG 1.4.11) */
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
Nicht allein auf Farbe verlassen:
<!-- ❌ Nur Farbe zeigt Fehler an -->
<input class="error-border">
<style>.error-border { border-color: red; }</style>
<!-- ✅ Farbe + Symbol + Text -->
<div class="field-error">
<input aria-invalid="true" aria-describedby="email-error">
<span id="email-error" class="error-message">
<svg aria-hidden="true"><!-- Fehler-Symbol --></svg>
Bitte geben Sie eine gültige E-Mail-Adresse ein
</span>
</div>
Medienalternativen (1.2)
<!-- Video mit Untertiteln -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="Englisch" default>
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="Beschreibungen">
</video>
<!-- Audio mit Transkript -->
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
</audio>
<details>
<summary>Transkript</summary>
<p>Vollständiger Transkripttext...</p>
</details>
Bedienbar
Tastaturzugänglichkeit (2.1)
Die gesamte Funktionalität muss per Tastatur zugänglich sein. Bevorzuge native interaktive Elemente – <button>, <a href> und Formularsteuerelemente übernehmen die Aktivierung per Eingabetaste/Leertaste, den Fokus und die assistive Technologie-Semantik automatisch. Füge nur dann manuelle Tastaturbehandlung hinzu, wenn du kein natives Element verwenden kannst.
<!-- ❌ Nicht-interaktives Element nur mit Klick: nicht fokussierbar, keine Tastaturaktivierung -->
<div class="card" onclick="handleAction()">Öffnen</div>
<!-- ✅ Am besten: natives Button-Element verwenden -->
<button type="button" onclick="handleAction()">Öffnen</button>
// ✅ Wenn du ein nicht-interaktives Element verwenden MUSST (z. B. div mit role="button"),
// mache es fokussierbar UND behandle die Tastaturaktivierung. Füge dies NICHT zu einem nativen
// <button> hinzu – Enter/Leertaste lösen bereits einen Klick aus, sodass du doppelt auslösen würdest.
element.setAttribute('role', 'button');
element.setAttribute('tabindex', '0');
element.addEventListener('click', handleAction);
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAction();
}
});
Keine Tastaturfallen. Benutzer müssen mit der Tabulatortaste in jede Komponente hinein- und wieder hinaus navigieren können. Verwende das Modal-Focus-Trap-Muster für Dialoge – das native <dialog>-Element erledigt dies automatisch.
Sichtbarer Fokus (2.4.7)
/* ❌ Niemals Fokus-Ringe entfernen */
*:focus { outline: none; }
/* ✅ :focus-visible für tastaturnur-Fokus verwenden */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid currentColor; /* erbt Textfarbe → bereits auf Kontrast geprüft */
outline-offset: 2px;
}
/* ✅ Oder eine Markenfarbe wählen und ≥3:1-Kontrast zu jedem Hintergrund prüfen */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
Fokus nicht verdeckt (2.4.11) – neu in 2.2
Wenn ein Element den Tastaturfokus erhält, darf es nicht vollständig durch andere vom Autor erstellte Inhalte wie sticky-Header, Footer oder überlappende Panels verdeckt werden. Auf Stufe AAA (2.4.12) darf kein Teil des fokussierten Elements verborgen sein.
/* ✅ Sticky-Header beim Scrollen zu fokussierten Elementen berücksichtigen */
:target {
scroll-margin-top: 80px;
}
/* ✅ Sicherstellen, dass fokussierte Elemente fixierte/statische Balken freigeben */
:focus {
scroll-margin-top: 80px;
scroll-margin-bottom: 60px;
}
Sprunglinks (2.4.1)
Biete einen Sprunglink an, damit Tastaturnutzer repetitive Navigation umgehen können. Siehe das Sprunglink-Muster für vollständiges Markup und Styles.
Zielgröße (2.5.8) – neu in 2.2
Interaktive Ziele müssen mindestens 24 × 24 CSS-Pixel (AA) groß sein. Ausnahmen: Inline-Textlinks, Elemente, deren Größe vom Browser gesteuert wird, und Ziele, bei denen ein 24 Pixel großer Kreis, der um die Begrenzungsbox zentriert ist, kein anderes Ziel überlappt.
/* ✅ Mindestzielgröße */
button,
[role="button"],
input[type="checkbox"] + label,
input[type="radio"] + label {
min-width: 24px;
min-height: 24px;
}
/* ✅ Komfortable Zielgröße (empfohlen 44×44) */
.touch-target {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
Ziehbewegungen (2.5.7) – neu in 2.2
Jede Aktion, die ein Ziehen erfordert, muss eine Einzelzeigeralternative haben (z. B. Schaltflächen, Eingabefelder). Siehe das Ziehbewegungen-Muster für ein Beispiel einer sortierbaren Liste.
Zeitlimits (2.2)
// Benutzern erlauben, Zeitlimits zu verlängern
function showSessionWarning() {
const modal = createModal({
title: 'Sitzung läuft ab',
content: 'Ihre Sitzung läuft in 2 Minuten ab.',
actions: [
{ label: 'Sitzung verlängern', action: extendSession },
{ label: 'Abmelden', action: logout }
],
timeout: 120000
});
}
Bewegung (2.3)
/* Präferenz für reduzierte Bewegung respektieren */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Verständlich
Seitensprache (3.1.1)
<!-- ❌ Keine Sprache angegeben -->
<html>
<!-- ✅ Sprache angegeben -->
<html lang="de">
<!-- ✅ Sprachwechsel innerhalb der Seite -->
<p>Das französische Wort für Hallo ist <span lang="fr">bonjour</span>.</p>
Konsistente Navigation (3.2.3)
<!-- Navigation sollte über Seiten hinweg konsistent sein -->
<nav aria-label="Hauptnavigation">
<ul>
<li><a href="/" aria-current="page">Startseite</a></li>
<li><a href="/products">Produkte</a></li>
<li><a href="/about">Über uns</a></li>
</ul>
</nav>
Konsistente Hilfe (3.2.6) – neu in 2.2
Wenn ein Hilfemechanismus (Kontaktinformationen, Chat-Widget, FAQ-Link, Selbsthilfeoption) auf mehreren Seiten wiederholt wird, muss er jedes Mal in der gleichen relativen Reihenfolge erscheinen. Benutzer, die auf eine konsistente Platzierung angewiesen sind, sollten nicht auf jeder Seite nach Hilfe suchen müssen.
Formular-Beschriftungen (3.3.2)
Jedes Eingabefeld benötigt eine programmatisch zugeordnete Beschriftung. Siehe das Formular-Beschriftungen-Muster für explizite, implizite und anleitende Beispiele.
Fehlerbehandlung (3.3.1, 3.3.3)
Fehler mit role="alert" oder aria-live an Screenreader ankündigen, bei ungültigen Feldern aria-invalid="true" setzen und beim Absenden auf den ersten Fehler fokussieren. Siehe das Fehlerbehandlungs-Muster für vollständiges Markup und JS.
Redundante Eingabe (3.3.7) – neu in 2.2
Zwinge Benutzer nicht, Informationen, die sie bereits in derselben Sitzung angegeben haben, erneut einzugeben. Fülle aus früheren Schritten automatisch aus oder lasse Benutzer aus zuvor eingegebenen Werten auswählen. Ausnahmen: Sicherheitsbestätigungen und abgelaufene Inhalte.
<!-- ✅ Lieferadresse automatisch aus Rechnungsadresse übernehmen -->
<fieldset>
<legend>Lieferadresse</legend>
<label>
<input type="checkbox" id="same-as-billing" checked>
Identisch mit Rechnungsadresse
</label>
<!-- Felder werden automatisch ausgefüllt, wenn aktiviert -->
</fieldset>
Zugängliche Authentifizierung (3.3.8) – neu in 2.2
Anmeldevorgänge dürfen nicht auf kognitive Funktionstests angewiesen sein (z. B. ein Passwort merken, ein Rätsel lösen), es sei denn, mindestens eines der folgenden Kriterien ist erfüllt:
- Ein Kopieren-Einfügen- oder Autofill-Mechanismus ist verfügbar
- Es gibt eine alternative Methode (z. B. Passkey, SSO, E-Mail-Link)
- Der Test verwendet Objekterkennung oder persönliche Inhalte (nur AA; AAA entfernt diese Ausnahme)
<!-- ✅ Einfügen in Passwortfelder erlauben -->
<input type="password" id="password" autocomplete="current-password">
<!-- ✅ Passwortlose Alternativen anbieten -->
<button type="button">Mit Passkey anmelden</button>
<button type="button">Anmeldelink per E-Mail senden</button>
Robust
ARIA-Verwendung (4.1.2)
Nativ-Elemente bevorzugen:
<!-- ❌ ARIA-Rolle auf div -->
<div role="button" tabindex="0">Klick mich</div>
<!-- ✅ Natives Button-Element -->
<button>Klick mich</button>
<!-- ❌ ARIA-Checkbox -->
<div role="checkbox" aria-checked="false">Option</div>
<!-- ✅ Native Checkbox -->
<label><input type="checkbox"> Option</label>
Wenn ARIA benötigt wird, verwende die korrekten Rollen und Zustände. Siehe das ARIA-Tabs-Muster für ein vollständiges Tablist-Beispiel.
Live-Regionen (4.1.3)
Verwende aria-live-Regionen, um dynamische Inhaltsänderungen anzukündigen, ohne den Fokus zu verschieben. Siehe das Live-Regionen-Muster für das Markup und einen showNotification()-Helfer.
Test-Checkliste
Automatisierte Tests
Bevorzuge ein Live-Lighthouse-Audit, das direkt die fehlgeschlagenen gerenderten Knoten an den Agenten zurückgibt. Mit Chrome DevTools MCP ist dies lighthouse_audit. Andernfalls:
# Lighthouse-Barrierefreiheitsaudit
npx lighthouse https://example.com --only-categories=accessibility
# axe-core
npm install @axe-core/cli -g
axe https://example.com
Manuelle Tests
- Tastaturnavigation: Gesamte Seite mit Tab durchgehen, Eingabetaste/Leertaste zum Aktivieren verwenden
- Screenreader: Test mit VoiceOver (Mac), NVDA (Windows) oder TalkBack (Android)
- Zoom: Inhalt bei 200 % Zoom nutzbar
- Hoher Kontrast: Test mit Windows High Contrast Mode
- Reduzierte Bewegung: Test mit
prefers-reduced-motion: reduce - Fokus-Reihenfolge: Logisch und folgt der visuellen Reihenfolge
- Zielgröße: Interaktive Elemente erfüllen das Minimum von 24×24px
Siehe die Screenreader-Befehlsreferenz für VoiceOver- und NVDA-Tastenkombinationen.
Häufige Probleme nach Auswirkung
Kritisch (sofort beheben)
- Fehlende Formular-Beschriftungen
- Fehlender Bild-Alt-Text
- Unzureichender Farbkontrast
- Tastaturfallen
- Keine Fokus-Indikatoren
Schwerwiegend (vor Veröffentlichung beheben)
- Fehlende Seiten-Sprache
- Fehlende Überschriftenstruktur
- Nicht beschreibender Link-Text
- Automatisch abspielende Medien
- Fehlende Sprunglinks
Mittelschwer (bald beheben)
- Fehlende ARIA-Beschriftungen für Symbole
- Inkonsistente Navigation
- Fehlende Fehleridentifizierung
- Zeitlimits ohne Steuerung
- Fehlende Orientierungspunktregionen


