Accessibility yay! Accessibility nay…
Die Anforderung klingt zuerst einmal eindeutig: Unsere neu zu bauende Webanwendung soll barrierefrei zu nutzen sein. Insbesondere User, die einen Screenreader einsetzen, sollen nicht eingeschränkt oder gebremst werden. Die Idee war also, Accessibility von Anfang an mitzudenken, um nicht nachträglich mit großem Aufwand Barrieren wieder ausbauen zu müssen.
Long Story short: Die Priorität, die wir dem Thema einräumen wollten, blieb im frühen Entwicklungsprozess zumindest teilweise hinter anderen, sichtbareren Features zurück, auch wenn das Accessibility-Review in der späteren Testphase gar nicht mal so vernichtend ausfiel. Aber einige Dinge wären einfacher gewesen, hätte man sie wirklich von Anfang an im Design mitgedacht.
Exkurs: Der Standard und das Gesetz
Was Barrierefreiheit im Web bedeutet und wie sie qualifiziert wird, regeln die Web Content Accessibility Guidelines (WCAG). Der internationale Standard basiert auf vier Prinzipien: Perceivable, Operable, Understandable und Robust, aus denen sich Richtlinien ableiten, sowie konkrete Erfolgskriterien, die nicht verletzt werden dürfen. Die Minimalanforderungen definiert Level A, die Maximalanforderungen Level AAA. Für Konformität mit dem European Accessibility Act (EAA), der in Deutschland in Form des Barrierefreiheitsstärkungsgesetz (BFSG) Mitte 2025 in Kraft tritt und die Accessibility von digitalen Produkten und Dienstleistungen sicherstellen will, ist das Erreichen von Level AA Voraussetzung, und damit eine gute Orientierung bei der Entwicklung. Allerdings ist damit nicht automatisch gute Accessibility und Usability garantiert: Auch wenn kein Erfolgskriterium verletzt ist, kann ein User beim Testen der Anwendung noch einiges an Problemen aufdecken.
Use Native HTML!
The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect. Tim Berners-Lee (Begründer des WWW und Entwickler von HTML)
Diese Idee ist in viele Web Standards eingeflossen. Insbesondere HTML bringt einiges an Accessibility out-of-the-box mit, vorausgesetzt, man nutzt die Elemente, wie sie gedacht sind. Hilfreich zur Navigation mit Screenreader sind etwa die Landmarks:
<header>
<nav>
<main>
<aside>
<section>
<footer>
Oder auch die Headings in korrekter absteigender Reihenfolge, die Screenreader-Usern einen schnellen Überblick über die wichtigsten Abschnitte der Seite ermöglichen. Auch bei den interaktiven Elementen gibt es in der Regel eine klare Wahl und man sollte sich gut überlegen: Habe ich wirklich gute Gründe, davon abzuweichen? In unserem Projekt war meine Einschätzung auf diese Frage zum damaligen Zeitpunkt für einen speziellen Fall: ja (wie sie heute lauten würde, dazu später).
Beispiel: Link als Button
Für eine CSS-Klasse .truncate-badge
, die Text in einem Badge abschneiden soll, falls der Text nicht mehr in eine Zeile passt (vor allem bei mobilen Geräten ein häufiger Fall), verwende ich eine experimentelle Komposition von Eigenschaften, die im Zusammenspiel funktionieren sollen.
.truncate-badge {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
Im ersten Testfall war das Badge ein Link und das Ergebnis wie erwartet und gewünscht. Sehr gut. Der nächste Badge-Typ sollte allerdings nicht auf eine Seite verlinken, sondern einen nativen Dialog öffnen, ein relativ klarer Fall für einen <button>
. Das Ergebnis war wie erwartet und gewünscht… allerdings nur in Chromium Browsern; bei Firefox und Safari wurde das line-clamp
ignoriert.
Forschen nach der Ursache ergab in meiner Timebox keine Ergebnisse, so dass ich mich entschied, an der Stelle vorerst mit dem bewährten <a>
-Tag zu arbeiten. [1]
Allerdings soll der Link-Button natürlich nicht nur wie ein Button aussehen, sondern sich auch wie einer verhalten, um Usern von Screenreadern und Keyboards die gewohnte Nutzungserfahrung zu bieten.
Damit ein Screenreader den Link semantisch als Button behandelt, muss man dem Accessibility Tree, also der semantischen Repräsentation der Seite für Screenreader, die Rolle des Elements explizit mitgeben via ARIA-Attribut role="button"
. Der Screenreader erkennt nun das Element als Button, allerdings benimmt er sich noch lange nicht wie einer.
Das korrekte Verhalten nachzuziehen ist schon komplexer, weil ein nativer Button einiges Out-of-the-Box mitbringt:
1. Fokussierbar sein
Das kann eigentlich auch ein Link, allerdings nur im Fall, dass der Anchor-Tag auch noch eine Hypertext-Reference besitzt (<a href></a>
). Ohne Referenz (oder bei Verwendung eines <div>
) braucht das Element ein explizites Attribut: tabindex=0
, um mit der Tastatur fokussiert werden zu können.
2. Action auslösen via Klick, Enter oder Space
Was das Verhalten eines Buttons von dem eines Links (mit Referenz) unterscheidet: Er ist nicht nur fokussierbar und klickbar via Maus oder Enter-Taste, sondern auch via Leertaste. Und da der Link im Beispiel keine Referenz hat, muss das komplette Klickverhalten mit Javascript nachgebaut werden. (Spätestens jetzt beschleichen mich leise Zweifel an meiner Entscheidung zugunsten des Stylings…) Das lässt sich aber relativ straight-forward umsetzen, ein Click-Event für die Maus, ein Keydown-Event für die beiden Keyboard-Tasten. Fast. Es gibt ein Detail, das ich bei meiner Umsetzung zunächst nicht auf dem Schirm hatte: Die zwei Tastaturwege, um die Button-Action zu triggern, funktionieren auf unterschiedliche Weise. Nur Enter feuert schon beim Drücken (und dann immer wieder, solange die Taste gedrückt bleibt), die Space-Taste feuert genau einmal – beim Loslassen (Blogpost von Adrian Roselli dazu). Der Unterschied wird bei den meisten Actions nicht auffallen, weil bei einem routinierten Tastenanschlag nur ein Sekundenbruchteil zwischen dem Feuern liegt und das Ergebnis das Gleiche ist. Aber für die Fälle, wo ein User beispielsweise eine Action kurzfristig verhindern und bei gedrückter Leertaste den Fokus durch Tabben verschieben will, lässt eine Keydown-Implementierung wie bei Enter das nicht zu. Für die Leertaste braucht es also ein Keyup-Event, um unerwartetes Buttonverhalten – und frustrierte User – auszuschließen:
document.addEventListener('DOMContentLoaded', () => {
const buttons = document.querySelectorAll('[role="button"]');
buttons.forEach(button => {
button.addEventListener('keydown', event => {
if (event.key === 'Enter') {
event.preventDefault();
console.log("Action via Enter")
button.click();
} else if (event.key === ' ') {
event.preventDefault(); // Verhindert das standardmäßige Scrollen über den Screen via Leertaste
}
});
button.addEventListener('keyup', event => {
if (event.key === ' ') {
console.log("Action via Space")
event.preventDefault();
button.click();
}
});
});
});
3. Und so weiter…
Das war noch nicht alles, was der <button>
standardmäßig mitbringt: Es lassen sich zum Beispiel via disabled
-Attribut seine Funktionen ausknipsen. Oder als type="submit"
die Daten eines Formulars schicken. [2]
Für den Dialog-Anwendungsfall ist das nicht nötig, aber auch mit dem nur ausschnittsweisen Nachbau des Button-Verhaltens wird klar, dass es gut überlegt sein will, ein HTML-Element zweckzuentfremden, statt das naheliegende Element zu nutzen.
Fazit
Habe ich bereut, es so umzusetzen? Nein, die Lektion wars definitiv wert und ich schätze „mein” HTML jetzt noch mehr als vorher. Würd ich es wieder tun? Nicht ausgeschlossen, aber die Argumente müssten schon sehr gut sein. Vermutlich würde ich nächstes Mal einmal mehr die Frage diskutieren, ob es nicht doch eine Lösung gibt, die mit nativen Komponenten auskommt, ohne dabei auf ein gutes Design verzichten zu müssen.
P.S.
Allein dieser eine, kleine Button muss mindestens drei WCAG-Erfolgskriterien sicherstellen: 2.1.1 Keyboard-Bedienbarkeit, 2.4.7 Sichtbarer Fokus und 4.1.2 Name, Role und Value.
-
Es gibt auch andere Truncating-Lösungen. Die, die ich getestet hatte, hätten allerdings einen Frontend-Umbau erfordert, dessen Aufwand ich als größer einschätzte als den Umbau der Button-Komponente ↩
-
auch nicht berücksichtigt habe ich den Forced Color Mode (z.B. High Contrast Mode in Windows, dem man mit CSS begegnen kann), in dem unter Umständen
<button>
und<a>
farblich unterschiedlich dargestellt werden. ↩