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.

Website prüfen
Beispiel im Prüfbericht Markierter Befund
Markierter Befund · Kritisch · WCAG 1.3.1Listbox ohne erforderliche Option

Rolle folgt Verhalten

Nennt es ein Ziel oder einen Befehl?

Diese eine Frage entscheidet zwischen Navigation und ARIA-Menü.

Illustratives BedienmodellZwei Oberflächen, zwei Tastaturverträge
Was tut der Eintrag?Er wechselt die Seite oder führt eine Aktion aus.
A

Wechselt URL oder Seitenbereich

Navigation als Linkliste

StartBerichteEinstellungen

<nav> <ul> <a>

TabLinkTabLink

Jeder Link bleibt ein eigener Tabstopp. Enter folgt dem Ziel. Eine Aufklapp-Navigation nutzt zusätzlich einen echten Schalter mit aria-expanded.

B

Verändert etwas im Arbeitsbereich

Anwendungsmenü als Widget

Aktionen
DuplizierenArchivierenLöschen
buttonmenumenuitem × 3
TabSchalterEinträge

Im 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

Seitenzielenav + ul + a

Tab erreicht jeden Link. Die Browserbedienung reicht.

Aufklappbare Seitenzielebutton + aria-expanded + ul

Die Linkliste bleibt eine Linkliste. Escape kann sie schließen.

Befehle im Produktbutton + menu + menuitem

Sie übernehmen Pfeiltasten, Fokus, Aktivierung und Rückgabe.

Bleibende Auswahlselect oder listbox

Ein 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 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.

Anleitung: ARIA-Elemente richtig einordnen

Zum Mitnehmen

Das Sortiermenü, korrigiert

Einträge auszeichnen, Beziehung herstellen, Links ohne Menürolle lassen.

Korrektur in 3 Schritten ansehen
  1. 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ü.

  2. 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ü.

  3. 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“.

Werbung

Kostenloser Website-Barrierefreiheits-Check

Website auf Barrierefreiheit prüfen.

Technische WCAG-Warnsignale erkennen. Kritische Stellen zuerst prüfen.

Der Website-Check ist bereit.

Werbung

Ein letzter Schritt

Prüfen, bevor Sie gehen.

Finden Sie automatisch erkennbare Barrieren auf Ihrer eigenen Website.

Website prüfen

Kostenlos starten · keine Installation

Automatisches Screening. Tastaturbedienung und Screenreader bleiben manuelle Prüfung.