Die erste Regel von ARIA in der Praxis
Falsch eingesetztes ARIA macht Webseiten oft weniger zugänglich statt barrierefreier. Dieser Artikel zeigt die häufigsten ARIA-Fehler in echten Frontends, von redundanten Rollen über falsches aria-hidden bis zu fehlenden Pflicht-States, und liefert einen praktischen Vorher-Nachher-Audit für Magento- und Hyvä-Projekte.
Inhaltsverzeichnis
- 1. Die erste Regel von ARIA: kein ARIA ist besser als schlechtes ARIA
- 2. Redundante Rollen auf nativen Elementen
- 3. aria-hidden auf fokussierbaren Inhalten
- 4. Fehlende Pflicht-States und -Properties
- 5. Rollen-Missbrauch: div und span statt semantischer Elemente
- 6. Live Regions: aria-live richtig einsetzen
- 7. Labeling: aria-label, aria-labelledby und aria-describedby
- 8. ARIA in Hyvä-Komponenten: Modals, Dropdowns und Tabs mit Alpine.js
- 9. ARIA-Audit: Fehler im Vorher-Nachher-Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Die erste Regel von ARIA: kein ARIA ist besser als schlechtes ARIA
Die erste Regel von ARIA, formuliert in der offiziellen WAI-ARIA Authoring Practices Guide, lautet sinngemäß: Wenn ein natives HTML-Element oder -Attribut die gewünschte Semantik und Interaktion bereits bietet, sollte man es verwenden, statt ein Element umzuwidmen und mit ARIA-Rollen nachzurüsten. Das klingt trivial, wird in der Praxis aber ständig missachtet. Entwickler greifen zu ARIA, weil es mächtig wirkt und viele Tutorials es als Allzwecklösung präsentieren, dabei ist ARIA ausschließlich eine Übersetzungsschicht für die Accessibility-API des Betriebssystems. Es verändert weder das Aussehen noch das native Tastaturverhalten eines Elements.
Genau darin liegt die Gefahr: ARIA kann einem Screenreader Informationen mitteilen, die durch das tatsächliche Verhalten der Seite nicht gedeckt sind. Ein <div role="button"> ohne Tastatur-Handler meldet sich als Button, reagiert aber nicht auf Enter oder Leertaste. Für Screenreader-Nutzer ist das schlimmer als gar kein ARIA, weil die Ankündigung eine Erwartung weckt, die die Seite nicht erfüllt. Jeder der folgenden Abschnitte behandelt ein konkretes Fehlermuster, das in echten Magento- und Hyvä-Frontends regelmäßig auftaucht, mit einer klaren Korrektur.
2. Redundante Rollen auf nativen Elementen
Der wohl häufigste ARIA-Fehler ist die redundante Rolle: <button role="button">, <nav role="navigation"> oder <input type="checkbox" role="checkbox">. Native HTML-Elemente bringen ihre implizite ARIA-Rolle bereits mit dem Accessibility Tree des Browsers mit. Eine zusätzliche role-Angabe fügt keine Information hinzu, erhöht aber das Risiko widersprüchlicher Zustände, wenn später Code geändert wird und das Attribut nicht synchron mitgepflegt wird. Statische Analyse-Tools wie eslint-plugin-jsx-a11y markieren solche redundanten Rollen inzwischen standardmäßig als Warnung.
Problematischer wird es, wenn eine Rolle die native Semantik sogar überschreibt und dabei verändert, etwa <h2 role="presentation">, was die Überschrift für Screenreader-Nutzer vollständig aus der Dokumentstruktur entfernt. In Magento-Templates passiert das oft unbeabsichtigt, wenn ein CMS-Block-Snippet unreflektiert aus einer Design-Vorlage übernommen wird. Die Regel für den Audit ist einfach: Jede role-Angabe auf einem nativen Element ist ein Kandidat zur Entfernung, sofern sie nicht bewusst die Semantik in eine andere, gewollte Richtung ändert, etwa role="presentation" auf einer rein layout-bedingten Tabelle.
<!-- WRONG: redundant roles duplicate native semantics -->
<button role="button" type="submit">Save</button>
<nav role="navigation" aria-label="Main menu">...</nav>
<input type="checkbox" role="checkbox" id="newsletter">
<h3 role="heading" aria-level="3">Product details</h3>
<!-- RIGHT: native elements already expose the correct role -->
<button type="submit">Save</button>
<nav aria-label="Main menu">...</nav>
<input type="checkbox" id="newsletter">
<h3>Product details</h3>
<!-- Exception: role is justified when it deliberately changes semantics -->
<table role="presentation">
<!-- purely layout table, not tabular data -->
<tr><td><img src="logo.svg" alt="Mironsoft"></td></tr>
</table>
3. aria-hidden auf fokussierbaren Inhalten
aria-hidden="true" entfernt ein Element vollständig aus dem Accessibility Tree, sodass Screenreader es überspringen. Das ist nützlich für rein dekorative Icons oder doppelte Inhalte. Der klassische ARIA-Fehler entsteht, wenn aria-hidden auf einen Container gesetzt wird, der ein fokussierbares Element enthält, etwa einen Link, Button oder ein Formularfeld. Das Element verschwindet zwar aus der Ausgabe des Screenreaders, bleibt aber per Tab-Taste erreichbar. Ein Screenreader-Nutzer landet dann auf einem Element, das komplett stumm bleibt, ohne jede Ankündigung, was massiv verwirrend ist und in der WCAG-Erfolgskriterium 4.1.2 (Name, Role, Value) als Verstoß gilt.
Dieses Muster tritt typischerweise bei geschlossenen Mobile-Menüs, ausgeblendeten Modal-Hintergründen oder Karussells auf, deren nicht sichtbare Slides weiterhin im DOM stehen. Die korrekte Lösung hängt vom Zweck ab: Für vollständig aus der Interaktion entfernte Bereiche ist das native inert-Attribut die robustere Wahl, weil es zusätzlich zum Accessibility Tree auch den Tastaturfokus blockiert. Für Elemente, die nur visuell versteckt sind, aber wieder eingeblendet werden können, muss aria-hidden immer zusammen mit tabindex="-1" auf jedem enthaltenen fokussierbaren Kindelement gesetzt werden, oder besser: das Element wird komplett aus dem DOM entfernt, solange es unsichtbar ist.
<!-- WRONG: focusable link stays tabbable while hidden from screen readers -->
<div aria-hidden="true" class="hidden md:block">
<a href="/contact">Contact us</a>
</div>
<!-- RIGHT (still in DOM, needs to reappear later): remove from both trees -->
<div aria-hidden="true" class="hidden md:block">
<a href="/contact" tabindex="-1">Contact us</a>
</div>
<!-- BETTER: use inert for fully disabled regions (blocks focus AND a11y tree) -->
<div inert class="mobile-menu-closed">
<a href="/contact">Contact us</a>
</div>
<!-- BEST for content that toggles: don't render it at all when hidden -->
<template x-if="mobileMenuOpen">
<div>
<a href="/contact">Contact us</a>
</div>
</template>
4. Fehlende Pflicht-States und -Properties
Jede ARIA-Rolle aus der WAI-ARIA-Spezifikation definiert nicht nur eine Semantik, sondern auch eine Liste von erforderlichen und unterstützten States und Properties. role="checkbox" verlangt zwingend aria-checked, role="tab" verlangt aria-selected, ein Combobox-Pattern verlangt aria-expanded und aria-controls. Wird die Rolle gesetzt, aber der Pflicht-State vergessen, meldet der Screenreader das Element korrekt als Checkbox oder Tab, aber ohne jeden Zustand, was für Nutzer bedeutungslos ist. Genau dieses halbfertige ARIA ist der zweithäufigste ARIA-Fehler nach der redundanten Rolle.
Besonders kritisch ist das bei dynamischen Zuständen, die sich per JavaScript ändern, aber im Markup nur einmalig gesetzt werden. Ein Accordion-Header mit aria-expanded="false", dessen Wert beim Öffnen nicht per x-bind oder Alpine-Reaktivität aktualisiert wird, bleibt für den Screenreader dauerhaft als geschlossen markiert, selbst wenn der Inhalt sichtbar ist. Die Prüfung im Audit ist mechanisch: Für jede vorkommende ARIA-Rolle wird die offizielle Pflichtliste aus den WAI-ARIA Authoring Practices nachgeschlagen und gegen das tatsächliche Markup abgeglichen, inklusive der Frage, ob der State reaktiv aktualisiert wird.
5. Rollen-Missbrauch: div und span statt semantischer Elemente
Rollen-Missbrauch entsteht, wenn ein generisches Element wie <div> oder <span> per ARIA in eine interaktive Rolle verwandelt wird, ohne die native Tastaturbedienbarkeit nachzubauen. <div role="button">Speichern</div> ist per Definition fokussierbar über tabindex="0", aber ohne diesen Zusatz, ohne keydown-Handler für Enter und Leertaste und ohne sichtbaren Fokusring bleibt es für Tastaturnutzer unerreichbar. Ein <button> bekommt all das kostenlos vom Browser, ein <div role="button"> erfordert mindestens vier zusätzliche Implementierungsschritte, die in der Praxis fast nie vollständig umgesetzt werden.
Ein zweites, subtileres Muster ist die falsche Rollen-Hierarchie: role="list" auf einem Element, dessen direkte Kinder keine role="listitem" tragen, bricht die semantische Baumstruktur, die manche Screenreader für die Ankündigung „Liste mit 5 Einträgen“ benötigen. Sobald ein CSS-Reset mit list-style: none die native Listensemantik in manchen Browsern beeinflusst, verschärft sich dieses Problem zusätzlich, weshalb bei entstylten Listen explizit role="list" auf dem <ul> ergänzt werden sollte, ohne die <li>-Elemente anzufassen.
6. Live Regions: aria-live richtig einsetzen
Live Regions kündigen dynamische Inhaltsänderungen an, ohne dass der Nutzer den Fokus verlieren muss, etwa Formularfehler, Warenkorb-Updates oder Ladeindikatoren. Der häufigste ARIA-Fehler bei Live Regions ist die Wahl der falschen Dringlichkeitsstufe: aria-live="assertive" unterbricht sofort jede laufende Sprachausgabe des Screenreaders und sollte ausschließlich für zeitkritische Informationen wie Fehlermeldungen reserviert bleiben. Für weniger dringende Updates wie „Artikel wurde zum Warenkorb hinzugefügt“ ist aria-live="polite" die richtige Wahl, weil die Ankündigung wartet, bis die aktuelle Sprachausgabe beendet ist.
Ein zweiter Fehler betrifft den Zeitpunkt der Registrierung: Der Container mit aria-live muss bereits beim initialen Rendern im DOM vorhanden sein, damit der Screenreader ihn als Live Region erkennt. Wird das gesamte Element inklusive des aria-live-Attributs erst nachträglich per JavaScript eingefügt, wird die erste Änderung oft nicht angekündigt, weil der Screenreader die Region noch nicht registriert hat. Die robuste Lösung ist ein dauerhaft leerer Live-Region-Container im initialen Markup, dessen Textinhalt später per JavaScript aktualisiert wird, statt das komplette Element samt Attribut erst bei Bedarf einzufügen.
Ein dritter, oft übersehener Fehler ist aria-atomic="true" zu vergessen, wenn nur ein Teilbereich einer Live Region aktualisiert wird. Ohne dieses Attribut liest mancher Screenreader nur den geänderten Teilstring vor, etwa eine einzelne Zahl in „3 Artikel im Warenkorb“, statt den vollständigen, verständlichen Satz. Gerade bei Warenkorb-Zählern oder Preisanzeigen, die sich per Alpine.js-Reaktivität häufig ändern, führt das zu bruchstückhaften, unverständlichen Ansagen für Screenreader-Nutzer.
7. Labeling: aria-label, aria-labelledby und aria-describedby
Die drei Labeling-Attribute werden häufig verwechselt. aria-label liefert einen unsichtbaren, eigenständigen Namen für ein Element und überschreibt jeden sichtbaren Text vollständig, auch für sehende Nutzer mit Spracherkennungssoftware wie Dragon, die auf den sichtbaren Text angewiesen sind, um Elemente per Sprachbefehl anzuklicken. aria-labelledby referenziert stattdessen die ID eines bereits vorhandenen sichtbaren Elements und ist deshalb fast immer die bessere Wahl, weil sichtbarer und zugänglicher Text konsistent bleiben. aria-describedby ergänzt zusätzliche Beschreibungstexte, etwa Formularhinweise oder Fehlermeldungen, ohne den primären Namen zu verändern.
Ein häufiger ARIA-Fehler ist aria-label auf einem Icon-Button, dessen sichtbarer Text sich unabhängig ändert, etwa durch eine spätere Übersetzung oder ein CMS-Update, sodass Screenreader-Nutzer und sehende Nutzer am Ende unterschiedliche Informationen erhalten. Für visuelles CSS-Ausblenden von Text, der trotzdem für Screenreader erhalten bleiben soll, sollte ausschließlich die etablierte sr-only-Klasse verwendet werden, niemals display: none oder visibility: hidden, weil beide CSS-Eigenschaften das Element zusätzlich aus dem Accessibility Tree entfernen.
/* Visually hidden but still available to screen readers */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* WRONG for accessible text: these remove the element from the a11y tree too */
.hidden-wrong {
display: none; /* also removes from accessibility tree */
visibility: hidden; /* also removes from accessibility tree */
}
/* Focus ring must remain visible even when custom-styled */
.sr-only.focusable:focus {
position: static;
width: auto;
height: auto;
overflow: visible;
clip: auto;
}
8. ARIA in Hyvä-Komponenten: Modals, Dropdowns und Tabs mit Alpine.js
Hyvä-Themes bauen interaktive Komponenten fast ausschließlich mit Alpine.js, was ARIA-Reaktivität deutlich einfacher macht als in klassischen jQuery-Themes, weil x-bind States automatisch synchron mit dem tatsächlichen Anwendungszustand hält. Der typische ARIA-Fehler in Hyvä-Projekten ist trotzdem verbreitet: Ein Dropdown wird mit statischem aria-expanded="false" im Template ausgeliefert, während der eigentliche Öffnen/Schließen-Zustand nur über eine Tailwind-Klasse wie x-show gesteuert wird, ohne dass aria-expanded per x-bind:aria-expanded an dieselbe Alpine-Variable gekoppelt ist.
Für ein vollständig zugängliches Dropdown müssen mindestens vier Teile zusammenspielen: der Trigger-Button mit aria-haspopup und reaktivem aria-expanded, das Panel mit passender Rolle wie role="menu", Escape-Taste zum Schließen mit Rückgabe des Fokus auf den Trigger, und ein Fokus-Trap, solange das Panel offen ist. Genau diese Kombination liefert das Hyvä-Alpine-Plugin @alpinejs/focus mit der Direktive x-trap bereits fertig mit, weshalb es beim Bau eigener Modals und Mega-Menüs konsequent genutzt werden sollte, statt eine eigene, fehleranfällige Fokus-Logik zu schreiben.
// Alpine.js accessible dropdown component for a Hyvä template
document.addEventListener('alpine:init', () => {
Alpine.data('accessibleDropdown', () => ({
open: false,
toggle() {
this.open = !this.open;
},
close(returnFocus = true) {
this.open = false;
if (returnFocus) {
this.$refs.trigger.focus();
}
},
init() {
// keep aria-expanded in sync with the real state, never hardcode it
this.$watch('open', (value) => {
this.$refs.trigger.setAttribute('aria-expanded', value);
});
}
}));
});
| Situation | Fehlerhaftes ARIA | Korrektes Muster | Warum es wichtig ist |
|---|---|---|---|
| Nativer Button | <button role="button"> |
<button> |
Redundante Rolle erhöht nur Pflegeaufwand |
| Ausgeblendeter Bereich mit Link | aria-hidden ohne tabindex="-1" |
inert oder DOM-Entfernung |
Verhindert stillen Tastaturfokus |
| Custom-Dropdown | Statisches aria-expanded="false" |
x-bind:aria-expanded |
Screenreader kennt echten Zustand |
| Klickbarer Bereich | <div onclick> ohne Rolle |
<button> oder role+tabindex+keydown |
Tastatur- und Screenreader-Zugriff |
| Statusmeldung | Text-Update ohne aria-live |
Dauerhafte aria-live="polite"-Region |
Screenreader liest Änderung automatisch |
9. ARIA-Audit: Fehler im Vorher-Nachher-Vergleich
Ein systematischer ARIA-Audit beginnt nicht mit dem Lesen von Code, sondern mit dem Ausprobieren der Seite mit einem echten Screenreader, etwa NVDA unter Windows oder VoiceOver unter macOS, kombiniert mit reiner Tastaturbedienung ohne Maus. Automatisierte Tools wie axe-core oder Lighthouse finden zuverlässig strukturelle Fehler wie fehlende Pflicht-States oder falsche Rollen-Hierarchien, sie können aber nicht erkennen, ob eine Ankündigung inhaltlich sinnvoll ist oder ob ein Fokus-Trap tatsächlich funktioniert. Deshalb gehört die manuelle Prüfung zwingend zum vollständigen Audit-Workflow dazu.
In der Praxis empfiehlt sich ein zweistufiges Vorgehen: Zuerst automatisierte Regressionstests mit axe-core in der CI-Pipeline, die neue, offensichtliche Verstöße vor dem Merge abfangen. Danach eine manuelle Stichprobenprüfung der wichtigsten Nutzerflüsse, insbesondere Checkout, Warenkorb und Formulare, mit echter Tastatur- und Screenreader-Navigation. Die folgende Vergleichstabelle sowie der Beispiel-Report zeigen, wie ein solcher Audit typische Fehler kategorisiert und priorisiert.
{
"tool": "axe-core",
"url": "https://shop.example.com/checkout",
"violations": [
{
"id": "aria-hidden-focus",
"impact": "serious",
"description": "aria-hidden element contains a focusable descendant",
"nodes": [
{ "target": [".mobile-nav[aria-hidden='true'] a"], "failureSummary": "Fix: add tabindex=-1 or remove element from DOM while hidden" }
]
},
{
"id": "aria-required-attr",
"impact": "critical",
"description": "Required ARIA attribute is missing",
"nodes": [
{ "target": ["[role='tab']"], "failureSummary": "Fix: add aria-selected to every element with role=tab" }
]
},
{
"id": "aria-allowed-role",
"impact": "moderate",
"description": "ARIA role is not allowed for this element",
"nodes": [
{ "target": ["h2[role='presentation']"], "failureSummary": "Fix: remove role or use a non-heading element" }
]
}
]
}
Die Priorisierung folgt dem WCAG-Impact-Level: critical und serious Verstöße wie fehlende Pflicht-States oder fokussierbare, versteckte Elemente blockieren Kernfunktionen komplett und müssen vor jedem Release behoben werden. moderate Verstöße wie überflüssige Rollen verschlechtern die Erfahrung, verhindern aber keine Aufgabe vollständig, und lassen sich gebündelt im nächsten Sprint abarbeiten. Diese Priorisierung verhindert, dass ein Audit-Report mit hundert Einträgen das Team lähmt, statt konkrete nächste Schritte zu liefern.
Mironsoft
ARIA-Audits, Accessibility-Refactoring und barrierefreie Hyvä-Komponenten
ARIA-Fehler zuverlässig finden und beheben?
Wir prüfen eure Magento- und Hyvä-Frontends mit automatisierten Tools und echter Screenreader-Navigation, priorisieren Verstöße nach Impact und setzen die Korrekturen direkt in Alpine.js-Komponenten und Templates um.
ARIA-Audit
axe-core-Scans kombiniert mit manueller NVDA- und VoiceOver-Prüfung
Komponenten-Refactoring
Dropdowns, Modals und Tabs mit korrekten States und Fokus-Traps
CI-Integration
axe-core-Regressionstests fest in der Deployment-Pipeline verankert
10. Zusammenfassung
Die erste Regel von ARIA bleibt der wichtigste Leitsatz jedes Accessibility-Audits: Native HTML-Elemente verwenden, wo immer möglich, und ARIA nur dort einsetzen, wo native Semantik nicht ausreicht. Redundante Rollen auf <button> oder <nav> fügen keinen Nutzen hinzu und erhöhen nur das Pflegerisiko. aria-hidden auf Containern mit fokussierbaren Kindelementen erzeugt stille, unerreichbare Tastaturfallen, die mit inert oder konsequenter DOM-Entfernung vermieden werden. Fehlende Pflicht-States wie aria-checked oder aria-selected machen eine Rolle für Screenreader bedeutungslos, selbst wenn die Rolle selbst korrekt gesetzt ist.
Rollen-Missbrauch auf generischen <div>- und <span>-Elementen erfordert vier zusätzliche Implementierungsschritte, die native Elemente kostenlos mitbringen. Live Regions müssen von Anfang an im DOM stehen und die richtige Dringlichkeitsstufe verwenden. In Hyvä-Projekten synchronisiert x-bind ARIA-States zuverlässig mit dem echten Alpine.js-Zustand, wenn es konsequent eingesetzt wird. Ein zweistufiger Audit-Workflow aus automatisierten axe-core-Scans in der CI-Pipeline und manueller Screenreader-Prüfung deckt sowohl strukturelle als auch inhaltliche ARIA-Fehler zuverlässig auf.
Häufige ARIA-Fehler vermeiden, das Wichtigste auf einen Blick
Erste Regel von ARIA
Native Elemente verwenden, statt sie per role nachzubilden. Kein ARIA ist besser als schlechtes ARIA.
aria-hidden mit Bedacht
Nie auf Container mit fokussierbaren Kindern ohne tabindex="-1". Für vollständig blockierte Bereiche inert nutzen.
Pflicht-States prüfen
Jede Rolle hat verpflichtende States wie aria-checked oder aria-selected. Reaktiv aktualisieren, nicht statisch setzen.
Audit zweistufig
axe-core in der CI-Pipeline für Struktur, NVDA/VoiceOver manuell für echte Nutzererfahrung.