ARIA menu im Sortiermenü
Erst die Rolle klären, dann ARIA setzen
Menüknopf, Menüliste, Einträge: drei Stellen, deren Rollen zusammenpassen müssen.
Rolle folgt Verhalten
Nennt es ein Ziel oder einen Befehl?
Diese eine Frage entscheidet zwischen Navigation und ARIA-Menü.
Wechselt URL oder Seitenbereich
Navigation als Linkliste
<nav> <ul> <a>
Jeder Link bleibt ein eigener Tabstopp. Enter folgt dem Ziel. Eine Aufklapp-Navigation nutzt zusätzlich einen echten Schalter mit aria-expanded.
Verändert etwas im Arbeitsbereich
Anwendungsmenü als Widget
buttonmenumenuitem × 3Im geöffneten Menü bewegen Pfeiltasten den Fokus. Home und End springen, Escape schließt und gibt den Fokus zurück.
Faustregel: Eine Website-Navigation heißt umgangssprachlich Menü, braucht aber fast nie role="menu". Die Rolle ist für ein Widget mit Fokusverwaltung.
Der Rollencheck vor dem Code
nav + ul + aTab erreicht jeden Link. Die Browserbedienung reicht.
button + aria-expanded + ulDie Linkliste bleibt eine Linkliste. Escape kann sie schließen.
button + menu + menuitemSie übernehmen Pfeiltasten, Fokus, Aktivierung und Rückgabe.
select oder listboxEin Wert bleibt gewählt. Prüfen Sie zuerst, ob select genügt.
Vollständiges Grundmuster
Ein Sortiermenü mit echtem Fokusweg
Das Beispiel öffnet, sortiert drei Einträge, schließt und setzt den Fokus zurück.
<button type="button" id="sort-button"
aria-haspopup="menu" aria-expanded="false"
aria-controls="sort-menu">
Sortieren
</button>
<div id="sort-menu" role="menu"
aria-labelledby="sort-button" hidden>
<button type="button" role="menuitem"
data-sort="name" tabindex="-1">
Nach Name
</button>
<button type="button" role="menuitem"
data-sort="price" tabindex="-1">
Preis aufsteigend
</button>
</div>
<ul id="courses">
<li data-name="Töpfern" data-price="55">
Töpfern · 55 €
</li>
<li data-name="Holzschnitt" data-price="40">
Holzschnitt · 40 €
</li>
<li data-name="Fotolabor" data-price="48">
Fotolabor · 48 €
</li>
</ul>const trigger = document.querySelector('#sort-button');
const menu = document.querySelector('#sort-menu');
const items = [...menu.querySelectorAll('[role="menuitem"]')];
const courses = document.querySelector('#courses');
function openMenu(position = 0) {
menu.hidden = false;
trigger.setAttribute('aria-expanded', 'true');
items[position].focus();
}
function closeMenu(restoreFocus = true) {
menu.hidden = true;
trigger.setAttribute('aria-expanded', 'false');
if (restoreFocus) trigger.focus();
}
function sortCourses(key) {
const rows = [...courses.children];
rows.sort((a, b) => {
if (key === 'price') {
return Number(a.dataset.price) - Number(b.dataset.price);
}
return a.dataset.name.localeCompare(b.dataset.name, 'de');
});
rows.forEach((row) => courses.append(row));
}
trigger.addEventListener('click', () => {
menu.hidden ? openMenu() : closeMenu();
});
trigger.addEventListener('keydown', (event) => {
if (event.key === 'ArrowDown') {
event.preventDefault();
openMenu();
}
if (event.key === 'ArrowUp') {
event.preventDefault();
openMenu(items.length - 1);
}
});
menu.addEventListener('keydown', (event) => {
const current = items.indexOf(document.activeElement);
const last = items.length - 1;
const destinations = {
ArrowDown: (current + 1) % items.length,
ArrowUp: (current - 1 + items.length) % items.length,
Home: 0,
End: last,
};
if (event.key in destinations) {
event.preventDefault();
items[destinations[event.key]].focus();
}
if (event.key === 'Escape') {
event.preventDefault();
closeMenu();
}
});
menu.addEventListener('focusout', () => {
requestAnimationFrame(() => {
if (!menu.contains(document.activeElement)) closeMenu(false);
});
});
items.forEach((item) => {
item.addEventListener('click', () => {
sortCourses(item.dataset.sort);
closeMenu();
});
});Prüfmatrix für jeden Zustand
GeschlossenNur der Schalter ist erreichbar. aria-expanded="false".
GeöffnetDer erste Eintrag hat Fokus. aria-expanded="true".
Im MenüPfeile wechseln Einträge; Tab ist kein Ersatz dafür.
BeendetAktivierung oder Escape schließt. Der Fokus kehrt zum Schalter zurück.
Aus dem Prüfbericht
Drei Stellen, drei Befunde
Kritisch Das Sortiermenü hat keine Einträge Regel aria-required-children
role="menu" verlangt menuitem darunter. Ohne sie klingt das Menü leer, obwohl Einträge sichtbar sind.
Kritisch Ein Menüeintrag steht außerhalb des Menüs Regel aria-required-parent
role="menuitem" braucht ein menu oder menubar als Elternelement. Ein Wrapper dazwischen unterbricht die Beziehung.
Kritisch Eine Menürolle, die es nicht gibt Regel aria-roles
role="menue" wird verworfen. Für eine Linkliste im Kopf reicht nav mit einer Liste, ganz ohne Menürolle.
Zum Mitnehmen
Das Sortiermenü, korrigiert
Einträge auszeichnen, Beziehung herstellen, Links ohne Menürolle lassen.
Korrektur in 3 Schritten ansehen
Die Einträge als menuitem auszeichnen
role="menu" verlangt Einträge mit role="menuitem". Ohne sie bleibt die Struktur unvollständig und das Menü klingt leer.
KorrekturvergleichSchritt 1 · HTML Vorher
<div role="menu" aria-label="Sortieren"> <button type="button">Nach Preis</button> <button type="button">Nach Name</button> </div>Nachher
<div role="menu" aria-label="Sortieren"> <button type="button" role="menuitem">Nach Preis</button> <button type="button" role="menuitem">Nach Name</button> </div>Markierte Zeilen wurden geändert
So prüfen Sie es Ein Vorleseprogramm nennt beim Öffnen die Zahl der Einträge im Menü.
Einträge direkt ins Menü legen
Ein div zwischen Menü und Eintrag löst die Beziehung. Der Wrapper entfällt oder bekommt role="none".
KorrekturvergleichSchritt 2 · HTML Vorher
<div role="menu" aria-label="Sortieren"> <div class="reihe"> <button type="button" role="menuitem">Nach Preis</button> </div> </div>Nachher
<div role="menu" aria-label="Sortieren"> <div class="reihe" role="none"> <button type="button" role="menuitem">Nach Preis</button> </div> </div>Markierte Zeilen wurden geändert
So prüfen Sie es Sehen Sie im Zugänglichkeitsbaum nach. Der Eintrag hängt direkt unter dem Menü.
Für Links kein Menü verwenden
Eine Navigation aus Links ist kein Befehlsmenü. nav mit einer Liste braucht keine Rolle und bringt seine Bedienung mit.
KorrekturvergleichSchritt 3 · HTML Vorher
<div role="menue"> <a href="/preise">Preise</a> <a href="/kontakt">Kontakt</a> </div>Nachher
<nav aria-label="Hauptmenü"> <a href="/preise">Preise</a> <a href="/kontakt">Kontakt</a> </nav>Markierte Zeilen wurden geändert
So prüfen Sie es Lassen Sie sich die Bereiche der Seite auflisten. Dort steht „Hauptmenü, Navigation“.
