Häufige ARIA-Fehler vermeiden (Erste Regel von ARIA)
A11Y
WCAG
Barrierefreiheit · ARIA · WCAG · Screenreader
Häufige ARIA-Fehler vermeiden
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.

17 Min. Lesezeit ARIA · Rollen · States · Live Regions WCAG 2.2 · Hyvä Theme · Alpine.js

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.

11. FAQ: Häufige ARIA-Fehler vermeiden

1Was ist die erste Regel von ARIA?
Ein natives HTML-Element mit der gewünschten Semantik ist einem per ARIA umgewidmeten generischen Element immer vorzuziehen. ARIA ersetzt kein natives Tastaturverhalten.
2Warum ist role="button" auf einem nativen Button überflüssig?
Der native Button bringt seine Rolle bereits über den Accessibility Tree mit. Die zusätzliche Angabe fügt nichts hinzu, erhöht aber das Risiko widersprüchlicher Zustände.
3Warum darf aria-hidden nicht auf fokussierbare Elemente gesetzt werden?
Es entfernt Elemente nur aus dem Accessibility Tree, nicht aus der Tab-Reihenfolge. Screenreader-Nutzer landen auf stummen Elementen. inert oder DOM-Entfernung sind die robustere Lösung.
4Welche States braucht ein Custom-Dropdown zwingend?
Mindestens aria-haspopup und reaktives aria-expanded auf dem Trigger, eine passende Rolle wie role="menu" auf dem Panel, bei Auswahllisten zusätzlich aria-selected auf den Optionen.
5Warum ist ein div mit role="button" problematisch?
Die Rolle weckt Erwartungen, die ohne tabindex, Tastatur-Handler und Fokusring nicht erfüllt werden. Ein natives button-Element vermeidet das vollständig.
6polite oder assertive bei Live Regions?
assertive nur für zeitkritische Fehler, unterbricht die laufende Sprachausgabe sofort. polite wartet und eignet sich für Statusmeldungen wie Warenkorb-Updates.
7aria-label vs. aria-labelledby vs. aria-describedby?
aria-label liefert einen eigenständigen unsichtbaren Namen, aria-labelledby referenziert sichtbaren Text, aria-describedby ergänzt zusätzliche Beschreibung ohne den Namen zu ändern.
8Barrierefreies Dropdown mit Alpine.js in Hyvä?
aria-expanded per x-bind an den Alpine-State koppeln, Escape zum Schließen mit Fokus-Rückgabe, Fokus-Trap über x-trap aus @alpinejs/focus während das Panel offen ist.
9Welche Tools finden ARIA-Fehler automatisch?
axe-core und Lighthouse decken strukturelle Fehler zuverlässig auf. Inhaltliche Fragen wie sinnvolle Ankündigungen erfordern zusätzlich manuelle Screenreader-Prüfung.
10Kann ARIA eine Seite weniger zugänglich machen?
Ja, falsch gesetztes ARIA kann native Semantik überschreiben oder falsche Zustände melden. Deshalb gilt: kein ARIA ist besser als schlecht implementiertes ARIA.