Komponenten · Barrierefreiheit nach Framework
Barrierefreiheit in Angular
Angulars größte Eigenheit für die Barrierefreiheit ist strukturell: Jede Komponente erzeugt ein zusätzliches Element im DOM – das Host-Element mit dem Selektor-Namen. In Listen und Tabellen zerstört das die Semantik, wenn man es nicht mit einem Attribut-Selektor umgeht. Dafür bringt Angular mit dem CDK als einziges der großen Frameworks fertige Bausteine für Fokusfalle, Live-Ankündigungen und Fokusverwaltung mit.
Diese Seite behandelt die Angular-spezifischen Fälle. Die vier Probleme, die in jedem Framework gleich sind, stehen unter Barrierefreiheit nach Framework.
Das Wichtigste in Kürze
- Jede Komponente erzeugt ein Host-Element.
<app-zeile>zwischen<tbody>und<tr>bricht die Tabellensemantik. - Attribut-Selektoren lösen das:
selector: 'tr[app-zeile]'stattselector: 'app-zeile'. -
Das CDK-Paket
@angular/cdk/a11yliefertLiveAnnouncer,cdkTrapFocus,FocusMonitorundcdkAriaLive– fertige Bausteine, die es sonst nirgends gibt. - Nach dem Router-Wechsel Titel setzen und Fokus platzieren;
Titleund einNavigationEnd-Abo sind der übliche Weg. - Reactive Forms liefern Validierungszustände, aber keine Ansage – Fehlermeldungen brauchen
aria-describedbyund eine Live-Region. -
[innerHTML]umgeht jede Prüfung, wiev-htmlunddangerouslySetInnerHTML. - Template-Linting über
@angular-eslint/eslint-plugin-templatefängt fehlendealt-Attribute und Klick-Handler an nicht interaktiven Elementen ab. - Stand August 2026.
Host-Elemente: der Angular-typische Strukturfehler
Angular rendert für jede Komponente ein Element mit dem Selektornamen. In den meisten Fällen ist das harmlos – in Listen, Tabellen und Definitionslisten nicht:
<!-- Gerendert: ein app-zeile zwischen tbody und tr -->
<tbody>
<app-zeile>
<tr>…</tr>
</app-zeile>
</tbody>
Der Browser repariert das nicht sinnvoll, und die Tabelle verliert für Hilfsmittel ihre Struktur – der Zusammenhang von Kopf- und Datenzelle geht verloren, siehe Tabellen semantisch aufbauen und 1.3.1 Info und Beziehungen.
Die Lösung ist ein Attribut-Selektor: Die Komponente hängt sich an ein vorhandenes Element, statt ein eigenes zu erzeugen.
@Component({
selector: 'tr[app-zeile]', // statt 'app-zeile'
template: `<td>{{ name }}</td><td>{{ wert }}</td>`,
})
export class ZeileComponent { … }
<tbody>
<tr app-zeile *ngFor="let e of eintraege" [name]="e.name" [wert]="e.wert"></tr>
</tbody>
Dasselbe Muster gilt für li[app-eintrag], option[app-option] und jede andere Konstellation, in der der Elternteil ein bestimmtes Kind erwartet.
Das CDK-a11y-Paket
@angular/cdk/a11y ist der Grund, warum Angular bei den schwierigen Mustern oft schneller zum Ziel kommt als die Alternativen:
-
LiveAnnouncersagt Texte über eine verwaltete Live-Region an – ohne dass du sie selbst im DOM anlegst. Der übliche Einsatz: Filterergebnisse, Speicherbestätigungen, Fehlerzahlen. Zuständiges Kriterium: 4.1.3 Statusmeldungen. -
cdkTrapFocushält den Fokus in einem Bereich – die halbe Miete für jeden Dialog. -
FocusMonitorunterscheidet, ob der Fokus per Maus, Tastatur oder programmatisch kam. Damit lässt sich der Fokusring genau dort zeigen, wo er gebraucht wird, ohne ihn je zu entfernen; die Anforderung steht unter 2.4.7 Fokus sichtbar. -
cdkAriaLivemacht einen vorhandenen Bereich zur Live-Region, ohne Attribute von Hand zu setzen.
constructor(private live: LiveAnnouncer) {}
filtern(anzahl: number) {
this.live.announce(`${anzahl} Ergebnisse gefunden`, 'polite');
}
Trotzdem gilt auch hier die Reihenfolge aus Die erste Regel von ARIA: Ein natives <dialog> mit showModal() braucht keinen cdkTrapFocus, weil es die Fokusfalle selbst mitbringt.
Router: Titel und Fokus
this.router.events
.pipe(filter((e) => e instanceof NavigationEnd))
.subscribe(() => {
this.title.setTitle(`${this.route.snapshot.data['titel']} | Beispiel`);
// Ziel: eine h1 mit tabindex="-1" in der neuen Ansicht
(document.querySelector('h1[tabindex="-1"]') as HTMLElement)?.focus();
});
Angular bringt mit TitleStrategy seit Version 14 auch einen eigenen Mechanismus für den Seitentitel mit – die Fokusverwaltung bleibt in jedem Fall Handarbeit. Warum beides zusammengehört, steht unter 2.4.2 Seitentitel.
Formulare: Zustand ist keine Ansage
Reactive Forms liefern invalid, touched und errors – und setzen daraufhin gern eine rote Umrandung. Für Screenreader passiert dabei nichts. Drei Ergänzungen fehlen regelmäßig:
- Das Feld braucht ein verknüpftes Label – auch wenn eine Wrapper-Komponente es rendert, siehe Labels & Beschriftungen.
-
Die Fehlermeldung gehört über
aria-describedbyan das Feld und wird mitaria-invalid="true"begleitet. - Beim Absenden braucht es eine Zusammenfassung, die den Fokus bekommt oder angesagt wird – Muster unter Fehlermeldungen barrierefrei und 3.3.1 Fehlererkennung.
Rollen und ARIA über das host-Objekt
Wenn eine Komponente doch ein eigenes Element erzeugt, sollte es wenigstens die richtige Rolle tragen. Standalone-Komponenten setzen das über das host-Objekt, nicht über @HostBinding-Dekoratoren im Klassenkörper:
@Component({
selector: 'app-hinweis',
standalone: true,
host: {
'role': 'status',
'aria-live': 'polite',
'[attr.aria-atomic]': 'true',
},
template: `{{ text() }}`,
})
export class HinweisComponent {
text = input.required<string>();
}
Der Vorteil gegenüber einem <div role="status"> im Template: Es entsteht kein zusätzliches Element, und die Rolle sitzt genau dort, wo der Inhalt steht. Das ist zugleich die Antwort auf das Host-Element-Problem von oben – wo sich der zusätzliche Knoten nicht vermeiden lässt, gibt man ihm eine sinnvolle Rolle.
Bei statischen Werten gehören sie ohne Klammern in host ('role': 'status'), bei berechneten mit [attr.…]. Ein häufiger Fehlgriff ist [attr.aria-hidden]="false": Angular rendert daraus aria-hidden="false", was etwas anderes bedeutet als ein weggelassenes Attribut – für einen Wegfall gehört null gebunden.
So prüfst du eine Angular-Anwendung
- Template-Linting einschalten und die Meldungen abarbeiten.
- Den gerenderten DOM ansehen, nicht das Template – Host-Elemente tauchen nur dort auf. Der Struktur-Check macht Listen und Tabellen sichtbar.
- Eine Route wechseln und prüfen, ob Titel und Fokus mitkommen.
- Jedes Overlay mit der Tastatur bedienen:
Escape, Fokusfalle, Fokusrückgabe. - Ein Formular fehlerhaft absenden und mit NVDA gegenprüfen, ob die Meldung ankommt.
- Einen Filter auslösen und hören, ob der
LiveAnnouncergreift.
Angular Material: die drei Konfigurationsfehler
Angular Material ist sorgfältig gebaut, und trotzdem entstehen dort regelmäßig Befunde – nicht in der Bibliothek, sondern in ihrer Verwendung. Drei Fälle decken den Großteil ab:
Icon-Buttons ohne Namen. <button mat-icon-button> mit einem <mat-icon> darin hat keinen zugänglichen Namen; der Screenreader sagt „Schaltfläche“. Es braucht ein aria-label oder einen sr-only-Text – siehe Icons & SVGs.
<!-- Stumm -->
<button mat-icon-button><mat-icon>delete</mat-icon></button>
<!-- Benannt -->
<button mat-icon-button aria-label="Eintrag löschen">
<mat-icon aria-hidden="true">delete</mat-icon>
</button>
Formularfelder ohne <mat-label>. Wer stattdessen nur einen placeholder setzt, bekommt ein Feld ohne Namen, sobald jemand tippt – dasselbe Problem wie überall, siehe Labels & Beschriftungen.
Tabellen ohne Beschriftung und Sortieransage. mat-table erzeugt zwar gültige Tabellenstruktur, aber weder eine Beschriftung noch eine Rückmeldung beim Sortieren. Beides gehört ergänzt – die Muster stehen unter Sortierbare & filterbare Tabellen.
Die allgemeine Regel dahinter: Eine Bibliothek liefert korrektes Verhalten, keine Inhalte. Namen, Beschriftungen und Ankündigungen bleiben immer Aufgabe des aufrufenden Codes.
Mehrsprachigkeit: lang mitwechseln
Angulars i18n tauscht Texte aus, nicht das lang-Attribut des Dokuments. Bleibt es auf de, liest der Screenreader englische Oberflächen mit deutscher Aussprache vor – für die Zielgruppe unbrauchbar, und ein Verstoß gegen 3.1.1 Sprache der Seite.
Bei Build-Zeit-Übersetzung setzt Angular lang im index.html je Sprachvariante korrekt. Bei Laufzeitwechsel – etwa mit ngx-translate – muss es die Anwendung selbst tun:
wechsle(sprache: string) {
this.translate.use(sprache);
this.document.documentElement.lang = sprache;
}
Einzelne fremdsprachige Passagen im Text bekommen zusätzlich ein eigenes lang am umschließenden Element – 3.1.2 Sprache von Teilen. Das gilt auch für Fachbegriffe, die in einer Oberfläche stehen bleiben.
Häufiger Fehler in der Praxis
(click) auf einem div. Angular macht daraus keinen Fokus und keine Tastaturbedienung. Das Template-Linting meldet es – wenn es eingerichtet ist.
Material-Komponenten für geprüft halten. Angular Material ist gut gebaut, aber die Konfiguration entscheidet: Ein mat-icon-button ohne aria-label ist genauso stumm wie jeder andere Icon-Button, siehe Icons & SVGs.
ngIf statt hidden bei Fehlermeldungen. Wird die Meldung erst beim Absenden in den DOM gehängt, ohne dass eine Live-Region existiert, hört sie niemand.
Das Host-Element im Review übersehen. Es taucht nur im gerenderten DOM auf, nicht im Template – deshalb findet man es erst in den DevTools oder mit dem Struktur-Check.
Häufige Fragen
Muss ich für jede Komponente einen Attribut-Selektor nehmen?
Nein, nur dort, wo der Elternteil ein bestimmtes Kindelement erwartet: Tabellen, Listen, Definitionslisten, <select>. Überall sonst ist das zusätzliche Host-Element unkritisch.
Ist Angular Material barrierefrei?
Die Komponenten folgen weitgehend dem ARIA Authoring Practices Guide und bringen Tastaturmodelle mit. Das entbindet nicht von der eigenen Prüfung – die häufigsten Befunde entstehen bei der Konfiguration, nicht in der Bibliothek.
Brauche ich das CDK, wenn ich native Elemente nutze?
Für Dialoge nicht – <dialog> bringt Fokusfalle und Escape mit. Für Live-Ankündigungen ist LiveAnnouncer trotzdem bequem, weil er die Region verwaltet und Doppelansagen vermeidet.
Wie richte ich das Template-Linting ein?
Über @angular-eslint/eslint-plugin-template mit dem Accessibility-Regelsatz. Es prüft statisch und ersetzt keinen Testlauf – die Abgrenzung steht unter Prüfwerkzeuge im Vergleich.
Verwandte Themen
- Barrierefreiheit nach Framework – die vier gemeinsamen Probleme
- Barrierefreiheit in React und in Vue – dieselben Fragen, andere Werkzeuge
- Live-Regionen – was der
LiveAnnouncerintern tut - Komplexe Datentabellen – wo Host-Elemente am meisten anrichten
Quellen
- Angular CDK: a11y – LiveAnnouncer, FocusTrap und FocusMonitor
- Angular: Accessibility – die Empfehlungen des Projekts
- ARIA Authoring Practices Guide – die Referenzmuster für Widgets (W3C)