Barrierefreiheit
Kurz gesagt: Barrierefreiheit heißt, dass die Seite auch mit der Tastatur und einem Screenreader funktioniert: Der Browser baut aus dem HTML einen Baum mit Rolle, Name und Zustand jedes Elements.
Problem
Der Button, der kein Button ist
Die Produktseite funktioniert: Du klickst auf „In den Warenkorb“ und das Produkt landet im Warenkorb. Ein Kunde schreibt, dass er nichts kaufen kann. Er benutzt keine Maus: Er bewegt sich mit der Tastatur, und ein Screenreader sagt ihm, was auf der Seite steht.
Konsole
Klick in das Feld „Menge“ und drück Tab. Der Fokus überspringt „In den Warenkorb“ und springt zum Herz. Das Herz ist ein Button, aber der Screenreader sagt nur „Button“: Was er tut, bleibt unbekannt. Beide Elemente sehen gut aus. Der Fehler steckt in dem, was der Browser nicht zeichnet.
Die Idee
Der zweite Baum
Aus dem DOM baut der Browser noch einen Baum: den Barrierefreiheitsbaum. Er ist die Seite, wie sie die Menschen und Programme sehen, die nicht auf den Bildschirm schauen:
- ein Screenreader liest einem Menschen, der nicht sehen kann, die Seite vor;
- Sprachsteuerung: Jemand sagt „Klick auf In den Warenkorb“, und das Programm sucht einen Button mit diesem Namen;
- die Tastatur: Tab geht durch die Elemente, die man auslösen kann;
- automatische Tests wie die Testing Library suchen nach Rolle und Name. Die Sandbox hier prüft auch so.
Jeder Knoten dieses Baums hat drei Dinge:
- eine Rolle: was das Element ist: Button, Link, Überschrift, Feld, Bild;
- einen zugänglichen Namen: wie es heißt: „In den Warenkorb“;
- einen Zustand: ob es gedrückt, markiert oder deaktiviert ist.
Die Rolle kommt vom Tag: <button> ist ein Button, <a href> ist ein Link, <h1> ist eine Überschrift. <div> und <span> haben keine Rolle. Im Baum bleibt nur ihr Text.
Sieh es dir an
Sieh dir die zwei Bäume an
Eine Produktkarte mit drei „Buttons“. Achte rechts darauf, was von jedem übrig bleibt.
<body><article> <h2>Brot</h2> <img src="bread.svg" alt="Brot"> <label>Menge <input type="number"></label> <div onclick="add()">Hinzufügen</div> <button><svg>…</svg></button> <button>Hinzufügen</button></article></body>Schritt für Schritt, in Worten
- Links steht das DOM. Daraus baut der Browser einen zweiten Baum: den Barrierefreiheitsbaum. Ihn lesen Screenreader, Sprachsteuerung und Tests nach Rolle. <article> wird zur Rolle article.
- <h2> wird zu heading (Überschrift). Sein Name kommt vom Text darin: „Brot“.
- Ein Bild hat keinen Text darin. Sein Name kommt aus alt. Ohne alt hat das Bild keinen Namen.
- Das Feld bekommt die Rolle spinbutton (ein Zahlenfeld), und sein Name kommt aus <label>: „Menge“. <label> selbst steht nicht im Baum, nur sein Text.
- <div> hat keine Rolle. onclick ändert daran nichts. Übrig bleibt nur der Text „Hinzufügen“. Er ist kein Button, und die Tastatur erreicht ihn nicht.
- Das ist ein echter Button, aber darin steht nur ein Icon ohne Text. Der Name ist leer. Ein Screenreader sagt nur „Button“.
- Ein <button> mit Text: die Rolle button und der Name „Hinzufügen“. Tab erreicht ihn, und Enter und Leertaste lösen ihn aus.
- getByRole('button', { name: 'Hinzufügen' }) sucht im rechten Baum. Es findet einen Button: den echten. Das <div> und der Button ohne Namen existieren für diese Suche nicht.
Den Barrierefreiheitsbaum kannst du im Browser sehen: Öffne in den Entwicklertools (F12) „Elemente“ und das Panel Barrierefreiheit. Es zeigt Rolle und Namen des ausgewählten Elements.
Die Idee
Woher der Name kommt
Den Namen eines Elements bestimmen feste Regeln. Die häufigsten Quellen:
- der Text darin:
<button>In den Warenkorb</button>,<a href="/cart">Warenkorb</a>,<h1>; - das
alteines Bildes:<img src="logo.svg" alt="Der Shop">. Ein Link, in dem nur ein Bild steht, bekommt seinen Namen ausalt; - das
<label>eines Feldes: „Menge“ ist der Name des Feldes daneben; aria-label: ein Name, der direkt im Attribut steht, wenn es keinen sichtbaren Text gibt:<button aria-label="Zu den Favoriten hinzufügen">mit einem Icon darin;aria-labelledby: Der Name ist der Text eines anderen Elements, das peridangegeben wird.
aria-label und aria-labelledby sind stärker als der Text darin. Der Name sagt, was das Element tut, nicht wie es aussieht: „In den Warenkorb“, nicht „Einkaufswagen“. Bei einem Bild in einem Link sagt der Name, wohin der Link führt: „Der Shop“, nicht „Logo“.
Probier es aus
Gib ihnen Namen
Das Logo oben führt zur Startseite, ist aber ein Bild ohne alt. Der Warenkorb-Button ist nur ein Icon. Gib dem Link einen Namen („Der Shop“) und dem Button einen Namen („In den Warenkorb“). Die Prüfung findet sie nach Rolle und Name und drückt den Button.
Konsole
Lösung
index.html<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Frischmilch 3% · Der Shop</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<a href="/"><img src="img/logo.svg" alt="Der Shop" width="40" height="40"></a>
</header>
<main>
<article>
<h1>Frischmilch 3%</h1>
<p>Preis: 2,40 €</p>
<button type="button" aria-label="In den Warenkorb" onclick="console.log('In den Warenkorb gelegt: Frischmilch 3%')">
<svg viewBox="0 0 24 24" width="22" height="22"><path d="M3 4h3l2 11h11l2-8H7" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="9" cy="19" r="1.6"/><circle cx="18" cy="19" r="1.6"/></svg>
</button>
</article>
</main>
</body>
</html>
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
.add, button { font: inherit; padding: 6px 14px; border: 0; border-radius: 6px; background: #1f6f5c; color: white; cursor: pointer; }
.fav { background: none; color: #c2477f; padding: 4px; }
.fav svg { fill: currentColor; }
.add { display: inline-block; }
label { display: block; margin: 8px 0; }
input { width: 4em; }
Das Logo bekommt alt="Der Shop", und der Link nimmt seinen Namen daraus. Der Button bekommt aria-label="In den Warenkorb": Er hat keinen Text, aus dem ein Name kommen könnte. alt="Logo" besteht nicht: Wer „Logo, Link“ hört, weiß nicht, wohin er führt. aria-label="Warenkorb" besteht auch nicht: Der Button öffnet den Warenkorb nicht, er legt etwas hinein.
Die Idee
Die Tastatur und der Fokus
Nicht alle benutzen eine Maus. Manche haben zitternde Hände, manche sehen den Zeiger nicht, manche tippen mit der Tastatur schneller. Sie bewegen sich mit Tab durch die Seite und lösen mit Enter aus.
Der Fokus ist das Element, das gerade die Tasten bekommt. Tab schiebt ihn vor, Umschalt+Tab zurück. Tab erreicht nur Elemente, die man auslösen kann: Links mit href, Buttons, Felder, Dropdowns. Die Reihenfolge ist die DOM-Reihenfolge, von oben nach unten.
<button> bringt alles mit: eine Rolle, Fokus, und Enter und Leertaste lösen es aus. <div> hat nichts davon. Ihm Fokus und Enter beizubringen heißt, noch einmal <button> zu schreiben.
tabindex ändert, welches Element den Fokus bekommt:
tabindex="0": Das Element reiht sich in die Tab-Reihenfolge ein, an seiner Stelle im DOM;tabindex="-1": Tab überspringt es, aber Code kann es mitfocus()fokussieren;tabindex="1"und höher: Das Element kommt zuerst, vor allen anderen. Die Reihenfolge der Seite geht kaputt. Benutz das nicht.
Der Browser zeichnet einen Rahmen um das fokussierte Element. Ohne ihn weiß ein Tastaturnutzer nicht, wo er ist. Entferne ihn nicht. Wenn er dir nicht gefällt, ersetz ihn durch einen anderen.
Probier es aus
Mit der Tastatur
„In den Warenkorb“ ist ein <div>. Sorg dafür, dass Tab es nach dem Feld „Menge“ erreicht und dass Enter in den Warenkorb legt. Die Prüfung drückt zweimal Tab und dann Enter.
Konsole
Lösung
index.html<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Dobrudzha-Brot · Der Shop</title>
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
</head>
<body>
<main>
<h1>Dobrudzha-Brot</h1>
<p>Preis: 1,80 €</p>
<label>Menge <input type="number" value="1" min="1"></label>
<button type="button" class="add">In den Warenkorb</button>
</main>
</body>
</html>
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
.add, button { font: inherit; padding: 6px 14px; border: 0; border-radius: 6px; background: #1f6f5c; color: white; cursor: pointer; }
.fav { background: none; color: #c2477f; padding: 4px; }
.fav svg { fill: currentColor; }
.add { display: inline-block; }
label { display: block; margin: 8px 0; }
input { width: 4em; }
script.jsconst add = document.querySelector('.add');
const qty = document.querySelector('input');
add.addEventListener('click', () => {
console.log(`In den Warenkorb gelegt: ${qty.value} × Dobrudzha-Brot`);
});
<div class="add"> wird zu <button type="button" class="add">. Die Klasse bleibt, damit script.js den Button findet und das CSS ihn wie vorher zeichnet. Tab erreicht den Button nach dem Feld, weil er im DOM danach steht. Enter auf einem Button löst click aus, also funktioniert derselbe Listener. <div role="button" tabindex="0"> besteht nicht: Der Screenreader sagt „Button“ und Tab erreicht es, aber Enter tut nichts. tabindex="1" am Button besteht auch nicht: Tab geht zuerst dorthin, noch vor dem Feld.
Probier es aus
Finde den Fehler
Der Button funktioniert mit der Maus. Ein Tastaturnutzer drückt Tab und sieht nicht, wo er ist. Welche Zeile ist schuld?
Wähle die Zeile mit dem Fehler.
Zeile 3 entfernt den Fokusrahmen. Der Button bekommt den Fokus, aber nichts zeigt es. Lösch die Zeile oder setz einen anderen sichtbaren Rahmen: button:focus-visible { outline: 3px solid #f4c542; }.
Die Idee
Überschriften und die erste Regel von ARIA
Überschriften sind das Inhaltsverzeichnis der Seite. Ein Screenreader-Nutzer liest die Seite nicht von vorn. Er bittet um eine Liste der Überschriften oder springt mit einer Taste zwischen ihnen (H in NVDA und JAWS). Deshalb erzählen die Ebenen die Gliederung: ein <h1> für die Seite, <h2> für die Abschnitte, <h3> darin. Wähl nicht <h4>, nur weil es kleiner ist. Die Größe änderst du mit CSS.
ARIA (Accessible Rich Internet Applications) ist eine Menge von Attributen, die den Barrierefreiheitsbaum ändern: role, aria-label, aria-expanded, aria-live und andere. Sie sagen dem Screenreader etwas, das HTML allein nicht sagen kann.
ARIA ändert nur, was angesagt wird, nicht das Verhalten. role="button" auf einem <div> lässt den Screenreader „Button“ sagen, aber Tab erreicht es trotzdem nicht und Enter tut trotzdem nichts. Die Person hört „Button“, drückt, und nichts passiert. Das ist schlimmer als ein <div> ohne Rolle.
Deshalb lautet die erste Regel von ARIA: Wenn es ein HTML-Element gibt, das das kann, nimm es. <button>, nicht role="button". <a href>, nicht role="link". <nav>, nicht role="navigation". ARIA ist für die Fälle, in denen HTML kein solches Element hat oder es keinen sichtbaren Text gibt, aus dem ein Name kommen könnte.
Probier es aus
Ergänze
Welches Attribut gibt einem Button, der nur ein Icon hat, einen Namen?
<button type="button" ="Zu den Favoriten hinzufügen">
<svg viewBox="0 0 24 24">…</svg>
</button>aria-label: der Name, den der Screenreader sagt und mit dem Sprachsteuerung und Tests den Button finden.
Genauer, für Neugierige
Meldungen, die erscheinen. Nach „In den Warenkorb“ schreibt die Seite „Hinzugefügt: Dobrudzha-Brot“. Wer auf den Bildschirm schaut, sieht es. Der Screenreader bemerkt die Änderung nicht, weil der Fokus nicht dort ist. <p role="status"> (oder aria-live="polite") sagt: Wenn sich der Text hier ändert, lies ihn vor, sobald du mit dem Aktuellen fertig bist. Für dringende Fehler gibt es role="alert", das unterbricht. Das Element muss vor der Meldung auf der Seite sein: Nur sein Text ändert sich.
Die Sprache. <html lang="de"> sagt dem Screenreader, mit welcher Stimme er lesen soll. Bei falschem lang wird deutscher Text mit englischer Aussprache gelesen und ist nicht zu verstehen. Ein Wort in einer anderen Sprache im Text bekommt sein eigenes: <span lang="fr">Croissant</span>.
Verstecken. display: none und das Attribut hidden entfernen das Element vom Bildschirm und aus dem Barrierefreiheitsbaum. aria-hidden="true" entfernt es nur aus dem Baum: für Icons neben Text, die der Screenreader nicht sagen muss. Ein fokussierbares Element in aria-hidden ist ein Fehler: Tab erreicht es, und der Screenreader schweigt.
Die Reihenfolge der Namen. Den vollständigen Algorithmus beschreibt der Standard Accessible Name and Description Computation. Kurz: erst aria-labelledby, dann aria-label, dann das, was HTML liefert (<label>, alt, der Text darin), zuletzt title. title und placeholder sind schlechte Namen: title erscheint nur, wenn du mit der Maus darüberfährst, und placeholder verschwindet, sobald du tippst.
:focus und :focus-visible. :focus gilt immer, wenn das Element den Fokus hat, auch nach einem Mausklick. :focus-visible gilt, wenn der Browser findet, dass der Rahmen sichtbar sein soll: bei Tab, aber nicht nach einem Klick auf einen Button. Wenn du den Rahmen ändern willst, nimm also :focus-visible. CSS ist das nächste Modul.
Zum Inhalt springen. Der erste Link auf vielen Seiten ist „Zum Inhalt springen“ (<a href="#main">). Er bleibt versteckt, bis er den Fokus bekommt. So muss ein Tastaturnutzer nicht jedes Mal das ganze Menü durchgehen.
Übung
Der Warenkorb
Der Warenkorb wurde in Eile gebaut. „Warenkorb“ ist nur fetter Text, die „ד-Buttons sind <span>, und der Link „Zur Kasse“ hat tabindex="1", „damit er der erste ist“. Bring es in Ordnung: eine Überschrift der Ebene 1; Buttons, die Tab erreicht und Enter auslöst, mit den Namen „Entfernen Dobrudzha-Brot“ und „Entfernen Frischmilch 3%“; Tab in der Reihenfolge der Seite: der erste Button, der zweite, der Link. Die Prüfung geht nur mit der Tastatur durch den Warenkorb.
Konsole
Lösung
index.html<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Warenkorb · Der Shop</title>
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
</head>
<body>
<main>
<h1>Warenkorb</h1>
<ul>
<li>Dobrudzha-Brot, 2 × 1,80 € <button type="button" class="remove" aria-label="Entfernen Dobrudzha-Brot">×</button></li>
<li>Frischmilch 3%, 1 × 2,40 € <button type="button" class="remove" aria-label="Entfernen Frischmilch 3%">×</button></li>
</ul>
<p>Gesamt: 6,00 €</p>
<a href="/checkout">Zur Kasse</a>
</main>
</body>
</html>
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
.title { display: block; font-size: 1.6em; margin: 8px 0; }
li { margin: 6px 0; }
.remove { font: inherit; margin-left: 8px; padding: 0 8px; border: 1px solid #999; border-radius: 4px; background: white; cursor: pointer; }
script.jsfor (const remove of document.querySelectorAll('.remove')) {
remove.addEventListener('click', () => remove.closest('li').remove());
}
„Warenkorb“ wird zu <h1>. Jedes <span class="remove"> wird zu <button type="button" class="remove"> mit aria-label: Der sichtbare Text „ד sagt nicht, was der Button tut. Der Name sagt auch, welches Produkt er entfernt, weil zwei Buttons „Entfernen“ gleich klingen. tabindex="1" fällt weg: Der Link steht im DOM zuletzt und kommt für Tab zuletzt. Die Prüfung drückt Enter auf dem ersten Button und zählt die Produkte, deshalb besteht <span role="button" tabindex="0"> nicht.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Das HTML-Dokument · Text, Links, Bilder, Tabellen · Formulare
- Geht weiter in: Responsive Design · Eine Liste aus Daten · Daten aus einer API · Die erste Komponente
- Aufgaben zum Thema: HTML: Die Struktur der Seite