Komponenten · Interaktive Widgets

Barrierefreie Tabs: das APG-Pattern Schritt für Schritt

Barrierefreie Tabs bestehen aus drei ARIA-Rollen: role="tablist" für die Leiste, role="tab" für jeden Reiter und role="tabpanel" für die Inhaltsbereiche – der aktive Reiter trägt aria-selected="true" und als einziger tabindex="0". Pfeiltasten wechseln zwischen den Reitern, die Tab-Taste stoppt nur einmal in der Leiste, und JavaScript muss Fokus, Zustände und Sichtbarkeit selbst nachführen, denn ein natives HTML-Element für Tabs gibt es nicht.

Das Wichtigste in Kürze

  • Drei Rollen bilden das Muster: tablist (Leiste), tab (Reiter), tabpanel (Inhalt); der aktive Reiter trägt aria-selected="true".
  • Roving Tabindex: Nur der aktive Tab hat tabindex="0"; Pfeil links/rechts wechseln zyklisch, Pos1/Ende springen zum ersten bzw. letzten Reiter (APG: optional, BFIT: empfohlen).
  • Manuell aktivieren (Enter/Leertaste), wenn Panels nachladen – automatisch nur, wenn alle Inhalte bereits im DOM stehen (W3C APG).
  • Inaktive Panels bekommen hidden. Nur optisch Verstecktes lässt fokussierbare Elemente in der Tab-Reihenfolge – ein Verstoß gegen WCAG 2.4.3.
  • Vier Kriterien, alle Level A: 2.1.1 Tastatur, 1.3.1 Info und Beziehungen, 2.4.3 Fokus-Reihenfolge, 4.1.2 Name, Rolle, Wert.
  • BFIT-Handreichung: Beschriftungskontrast 4,5:1, Markierung des gewählten Reiters 3:1, Klickfläche mindestens 24 × 24 px, bedienbar bei 400 % Zoom.
  • WebAIM Million (Februar 2026): Startseiten mit ARIA haben im Schnitt 59,1 erkannte Fehler, ohne ARIA nur 42 – falsches ARIA schadet mehr, als es nützt.

Wann Tabs das richtige Muster sind

Tabs zeigen mehrere gleichrangige Ansichten desselben Themas auf demselben Platz – sichtbar ist genau eine. Vorher prüfe ich die Alternativen: eigene Unterseiten für eigenständig verlinkbare Inhalte (eigene URL, Zurück-Button, auffindbar für Suchmaschinen) und Akkordeons für viele oder lange Abschnitte, von denen mehrere offen sein dürfen – dafür gibt es mit <details> und <summary> native Elemente. Tabs bleiben für wenige kompakte Ansichten, die einander ersetzen: Versandarten, Monats- und Jahresansicht.

Für sie kennt HTML Stand Juli 2026 kein natives Element. Referenz ist deshalb das Tabs-Pattern des ARIA Authoring Practices Guide (APG) – einer der wenigen Fälle, in denen die erste Regel von ARIA ins Leere greift. Vorsicht bleibt angebracht: Startseiten mit ARIA haben laut WebAIM Million (Februar 2026) im Schnitt 59,1 erkannte Fehler, ohne ARIA nur 42. Umgekehrt gilt die Warnung der BFIT-Handreichung: Tab-Markup ist kein Akkordeon-Markup.

Das Markup: drei Rollen, zweifach verknüpft

<div class="tabs">
  <div role="tablist" aria-label="Versandarten">
    <button type="button" role="tab" id="tab-standard"
            aria-selected="true" aria-controls="panel-standard">
      Standard
    </button>
    <button type="button" role="tab" id="tab-express"
            aria-selected="false" aria-controls="panel-express" tabindex="-1">
      Express
    </button>
    <button type="button" role="tab" id="tab-abholung"
            aria-selected="false" aria-controls="panel-abholung" tabindex="-1">
      Abholung
    </button>
  </div>

  <div role="tabpanel" id="panel-standard" aria-labelledby="tab-standard" tabindex="0">
    <p>Lieferung in 2–4 Werktagen, versandkostenfrei ab 50 €.</p>
  </div>
  <div role="tabpanel" id="panel-express" aria-labelledby="tab-express" tabindex="0" hidden>
    <p>Zustellung am nächsten Werktag bei Bestellung bis 14 Uhr.</p>
  </div>
  <div role="tabpanel" id="panel-abholung" aria-labelledby="tab-abholung" tabindex="0" hidden>
    <p>Abholung in der Filiale, meist nach zwei Stunden bereit.</p>
  </div>
</div>

Vier Details entscheiden hier:

  • Name der Leiste: aria-label – oder aria-labelledby auf eine sichtbare Überschrift – nennt assistiven Technologien den Zweck der Gruppe.
  • Doppelte Verknüpfung: Jeder Tab zeigt per aria-controls auf sein Panel, jedes Panel per aria-labelledby zurück und wird nach seinem Reiter benannt – die programmatische Beziehung, die 1.3.1 Info und Beziehungen verlangt. Ohne sie hört ein Screenreader lose Bedienelemente.
  • Echte Buttons bringen Fokussierbarkeit und Aktivierung per Enter wie Leertaste mit.
  • tabindex="0" am Panel, wenn es keine fokussierbaren Elemente enthält.

ARIA ändert weder Darstellung noch Verhalten: Die Rollen sind ein Versprechen, das dein JavaScript einlösen muss (Rollen, States & Properties).

Das Tastaturmodell: ein Stopp, dann Pfeiltasten

Der Schlüssel ist der Roving Tabindex: Nur der aktive Reiter steht mit tabindex="0" in der Tab-Reihenfolge, alle anderen tragen tabindex="-1". Die Tab-Taste durchquert die Leiste so mit einem einzigen Stopp statt mit acht, innerhalb übernehmen die Pfeiltasten.

Taste Verhalten
Tab fokussiert den aktiven Reiter; der nächste Druck führt ins Panel
Pfeil rechts / links nächster bzw. voriger Reiter, zyklisch
Pos1 / Ende (optional) erster bzw. letzter Reiter
Enter / Leertaste aktiviert den fokussierten Reiter (manuelle Aktivierung)
Entf (optional) schließt einen schließbaren Reiter

Ein Detail aus dem Tabs-Pattern: Horizontale Leisten fangen Pfeil hoch/runter bewusst nicht ab – die Seite soll scrollen; erst aria-orientation="vertical" gibt ihnen die Navigation. Die BFIT-Handreichung verlangt abweichend alle vier Pfeiltasten. Und eines aus dem APG-Keyboard-Guide: deaktivierte Reiter bekommen aria-disabled="true" statt disabled und bleiben so fokussierbar (Tastatur und Fokus).

Automatisch oder manuell aktivieren?

Bei automatischer Aktivierung folgt das Panel dem Fokus, sobald ein Reiter per Pfeiltaste erreicht wird; bei manueller schalten erst Enter oder Leertaste um. Beide sind zulässig, auch nach der BFIT-Handreichung „Registerkartengruppe“.

Die APG-Dokumente wirken widersprüchlich – die Pattern-Seite empfiehlt automatische, das Manual-Activation-Beispiel manuelle Aktivierung. Die Bedingung löst das auf: Automatisch ist nur angemessen, wenn alle Panel-Inhalte im DOM stehen und ohne spürbare Verzögerung erscheinen. Lädt der Wechsel Inhalte nach, ist „Selection follows focus“ verheerend: Jeder Pfeiltasten-Druck löst einen Request aus, wer zum sechsten Reiter durchpfeilt, wartet fünfmal umsonst.

Meine Regel: manuelle Aktivierung als Standard, automatische nur bei statischen Inhalten. Der Fokus bleibt danach auf dem Reiter – die BFIT-Handreichung (Stand Juli 2026 in Version 0.5) verbietet den automatischen Fokussprung ins Panel als unerwartete Kontextänderung (Anforderung 569).

Das komplette Skript

Der folgende Code setzt beide Varianten um, funktioniert für beliebig viele Tab-Leisten pro Seite und liegt in der Snippet-Bibliothek zum Kopieren bereit.

class Tabliste {
  constructor(leiste, { automatisch = false } = {}) {
    this.tabs = [...leiste.querySelectorAll('[role="tab"]')];
    this.automatisch = automatisch;

    leiste.addEventListener('click', (ereignis) => {
      const tab = ereignis.target.closest('[role="tab"]');
      if (tab) this.aktiviere(tab);
    });
    leiste.addEventListener('keydown', (ereignis) => this.tastatur(ereignis));
  }

  tastatur(ereignis) {
    const aktuell = this.tabs.indexOf(document.activeElement);
    if (aktuell === -1) return;

    let ziel;
    switch (ereignis.key) {
      case 'ArrowRight': ziel = (aktuell + 1) % this.tabs.length; break;
      case 'ArrowLeft':  ziel = (aktuell - 1 + this.tabs.length) % this.tabs.length; break;
      case 'Home':       ziel = 0; break;
      case 'End':        ziel = this.tabs.length - 1; break;
      default: return; // Pfeil hoch/runter bewusst nicht abfangen – die Seite soll scrollen
    }

    ereignis.preventDefault();
    this.fokussiere(this.tabs[ziel]);
  }

  fokussiere(tab) {
    this.tabs.forEach((t) => { t.tabIndex = -1; }); // Roving Tabindex
    tab.tabIndex = 0;
    tab.focus();
    if (this.automatisch) this.aktiviere(tab);
  }

  aktiviere(tab) {
    for (const t of this.tabs) {
      const gewaehlt = t === tab;
      t.setAttribute('aria-selected', String(gewaehlt));
      t.tabIndex = gewaehlt ? 0 : -1;
      document.getElementById(t.getAttribute('aria-controls')).hidden = !gewaehlt;
    }
  }
}

// Manuelle Aktivierung als Standard; automatisch: true nur bei statischen Panels
document.querySelectorAll('[role="tablist"]').forEach((leiste) => {
  new Tabliste(leiste, { automatisch: false });
});

Enter und Leertaste brauchen keinen eigenen Code: Echte Buttons übersetzen beide in ein click-Event. Der default-Zweig lässt alle übrigen Tasten an den Browser weiterlaufen.

Zwei Wege, es falsch zu machen

Panels nur optisch versteckt

Inaktive Panels bekommen hidden oder display: none. Beides entfernt den Inhalt aus dem Accessibility-Tree und aus der Tab-Reihenfolge – genau das ist gefordert: Ausgeblendete Registerkarteninhalte dürfen keinen Tastaturfokus erhalten (BFIT-Anforderung 568, EN 301 549 Punkt 9.2.4.3). Die ältere Technik, Panels per aria-hidden="true" beiseitezuschieben, ist überholt: Das versteckt nur vor Screenreadern, nicht vor der Tastatur. Gefährlich sind alle rein visuellen Verstecke – position: absolute; left: -9999px, opacity: 0, height: 0: Links darin bleiben fokussierbar, der Fokus verschwindet in unsichtbarem Inhalt – ein Verstoß gegen 2.4.3 Fokus-Reihenfolge.

<!-- So bitte nicht: Links ohne Rollen, Panel nur optisch versteckt -->
<ul class="tabs">
  <li><a href="#" class="aktiv" onclick="zeige(0)">Standard</a></li>
  <li><a href="#" onclick="zeige(1)">Express</a></li>
</ul>

<div class="panel" style="position: absolute; left: -9999px;">
  <a href="/versand/express-bedingungen.html">Bedingungen für den Expressversand</a>
</div>

Dieses Muster scheitert mehrfach: Der Screenreader meldet nur zwei Links – nichts verrät Zusammengehörigkeit oder Auswahl, womit 4.1.2 Name, Rolle, Wert verfehlt ist. Enter folgt dem Link, hängt # an die URL und füllt die Fragment-Historie statt den Zurück-Button; Pfeiltasten tun nichts; das verschobene Panel behält seinen fokussierbaren Link. role="tab" darauflegen hilft nicht: Es überschreibt die Linkrolle, das Fragment- und Historienproblem bleibt (Buttons vs. Links).

Responsive: Wenn Tabs zum Akkordeon werden

Ab vier, fünf Reitern passt die Leiste bei 320 px Fensterbreite erfahrungsgemäß nicht mehr – je nach Beschriftungslänge und Schriftgröße. Dort greift 1.4.10 Reflow: bedienbar bei 400 % Zoom ohne horizontales Scrollen. Bewährt haben sich mehrzeilig umbrechende Leisten oder der Wechsel zu Akkordeon oder <select>. Reines CSS reicht nicht: Wer nur die Optik ändert, lässt role="tab" auf einem Akkordeon stehen – die Semantik lügt dann. Die Rollen müssen mitwechseln: aus dem Tab ein Button mit aria-expanded, die Panel-Rolle entfällt. Praktikabel: ein JS-Umbau am Breakpoint – oder gleich ein Akkordeon.

Was in einem Tab-Panel steht, ist indexiert – aber nicht gleichwertig

Eine Frage, die bei Tabs immer kommt: Findet Google den Text im ausgeblendeten Panel? Ja, sofern er im HTML steht und nur per CSS versteckt ist – genau so, wie es das Tab-Muster verlangt. Wird der Inhalt dagegen erst per fetch nachgeladen, wenn jemand klickt, existiert er für den Crawler beim ersten Besuch nicht.

Der zweite Teil der Antwort ist der wichtigere: Sichtbarer Inhalt zählt in der Praxis mehr als eingeklappter. Wer die Kernaussage einer Seite hinter Reiter drei versteckt, verschenkt sie doppelt – bei Menschen, die nur den ersten Reiter lesen, und bei KI-Systemen, die Antworten aus dem sichtbaren Hauptteil ziehen. Die Faustregel ist dieselbe wie bei der Suchintention: Die Antwort gehört nach oben, nicht in ein Panel.

WCAG, BITV-Test und BFIT: was geprüft wird

Vier Erfolgskriterien tragen das Muster, alle Level A – geprüft im BITV-Test:

WCAG-Kriterium BITV-Prüfschritt Tabs-Bezug
2.1.1 Tastatur 9.2.1.1 „Ohne Maus nutzbar“ Pfeiltasten statt Maus; Tastaturbedienung muss der Mausbedienung nicht gleichen
1.3.1 Info und Beziehungen kein eigener Prüfschritt – geprüft unter 9.4.1.2 Verknüpfung Tab ↔ Panel, Name der Gruppe
2.4.3 Fokus-Reihenfolge 9.2.4.3 kein Fokus in versteckten Panels
4.1.2 Name, Rolle, Wert 9.4.1.2 Rollen und aria-selected über die Accessibility-API

Der Prüfschritt 9.4.1.2 nennt Tabpanels ausdrücklich als Widget ohne natives HTML-Pendant. Dazu die Gestaltung: 1.4.1 Benutzung von Farbe verlangt neben der Farbe eine zweite Kennzeichnung des gewählten Reiters, etwa eine Kante oder Unterstreichung. Die BFIT-Handreichung konkretisiert das in den Anforderungen 560 bis 582: Beschriftungskontrast 4,5:1, Markierung des gewählten Reiters mindestens 3:1 gegen Nachbarflächen (1.4.11 Nicht-Text-Kontrast), sichtbarer Fokusindikator, Klickflächen ab 24 × 24 px (2.5.8 Zielgröße), bedienbar bei 400 % Zoom – prüfbar mit dem Kontrast-Check.

So testest du es

  1. Tab-Taste: Fokus auf dem aktiven Reiter; der nächste Druck führt ins Panel, nicht zum nächsten Reiter.
  2. Pfeil rechts am letzten Reiter: Der Fokus springt zyklisch zum ersten; Pos1 und Ende erreichen, sofern implementiert, erste bzw. letzte Position.
  3. Aktivierung: Manuell wechselt das Panel erst mit Enter oder Leertaste, der Fokus bleibt auf dem Reiter.
  4. Weitertabben: Der Fokus darf in keinem versteckten Panel verschwinden; sonst fehlt hidden.
  5. NVDA: Zu hören sein muss „Versandarten Registerkarte, Express Registerkarte, 2 von 3“ – beim gewählten Reiter zusätzlich „ausgewählt“; das Rollenwort fällt laut Soll-Muster der BFIT-Handreichung zweimal, für tablist und für tab; bedient wird im Fokusmodus (Mit NVDA testen, Wie Screenreader-Nutzer surfen).
  6. DevTools-Accessibility-Baum: Rolle tab, Zustand selected, Gruppenname.
  7. 400 % Zoom: Leiste und Panels ohne horizontales Scrollen bedienbar.

Mehr Testrezepte: Selbst testen.

Häufiger Fehler in der Praxis

Der halbe Roving Tabindex. Am häufigsten sehe ich Leisten, in denen alle Reiter tabindex="0" behalten: Man tabbt durch jeden einzelnen, die Pfeiltasten tun nichts – Markup aus dem APG kopiert, JavaScript nur für Klicks. Rollen ohne Verhalten sind schlimmer als keine: Die Ansage „Registerkarte“ weckt Erwartungen, die die Komponente nicht einlöst.

aria-selected friert ein. Beim Wechsel toggelt das Skript nur eine CSS-Klasse: Optisch stimmt alles, der Screenreader meldet dauerhaft den ersten Reiter als ausgewählt – im Accessibility-Baum fällt das sofort auf.

Tab-Rollen auf dem Akkordeon. In einem Relaunch blieben die Rollen an der mobil als Akkordeon gestylten Ansicht stehen: NVDA kündigte Registerkarten an, die sich wie Aufklappflächen verhielten – Semantik-Lügen kosten das Vertrauen in alle übrigen Ansagen der Seite.

Automatische oder manuelle Aktivierung – im Komponenten-Baukasten lässt sich der Unterschied umschalten statt nachlesen, samt Code und Testprotokoll für beide Varianten.

Häufige Fragen

Wie macht man Tabs barrierefrei?

Mit den drei ARIA-Rollen tablist, tab und tabpanel, aria-selected="true" auf dem aktiven Reiter und der doppelten Verknüpfung über aria-controls und aria-labelledby – dazu JavaScript für Roving Tabindex, Pfeiltasten und das Ausblenden per hidden. Maßstab ist das Tabs-Pattern des W3C ARIA Authoring Practices Guide; ein natives HTML-Element für Tabs existiert nicht.

Was ist ein Roving Tabindex?

Eine Fokus-Technik für zusammengesetzte Komponenten: Genau ein Element der Gruppe hat tabindex="0", alle anderen tabindex="-1". Die Tab-Taste stoppt dadurch nur einmal, Pfeiltasten bewegen den Fokus innerhalb – das Skript setzt das alte Element auf −1, das neue auf 0 und ruft focus() auf. Anders als bei aria-activedescendant scrollt der Browser das fokussierte Element automatisch in Sicht.

Soll ein Tab schon beim Fokussieren aktiviert werden oder erst mit Enter?

Erst mit Enter oder Leertaste, sobald Panel-Inhalte nachgeladen werden – sonst löst jeder Pfeiltasten-Druck einen Netzwerk-Request aus, was die APG als verheerend einstuft. Stehen alle Inhalte im DOM und erscheinen ohne Verzögerung, ist automatische Aktivierung zulässig und spart einen Tastendruck. WCAG-konform sind beide Varianten.

Tabs oder Akkordeon – was ist barrierefreier?

Beide lassen sich barrierefrei bauen – das Akkordeon ist aber einfacher, weil es mit <details>/<summary> native Elemente gibt und keine Pfeiltasten-Mechanik braucht. Tabs lohnen sich für wenige gleichrangige Ansichten, von denen genau eine sichtbar sein soll; bei vielen oder langen Abschnitten und auf Mobilgeräten ist das Akkordeon die robustere Wahl.

Verwandte Themen

Gratis E-Book PDF Das Praxishandbuch

Kostenloses E-Book

Das Praxishandbuch für sauberes, zugängliches Web

Alles rund um semantisches HTML, Barrierefreiheit, WCAG & BFSG, GEO und SEO — praxisnah und am echten Code. In mehreren Feedbackschleifen von Leserinnen und Lesern verbessert.

  • 3.000+ Downloads
  • 7. Auflage
  • 37 Seiten
  • PDF

Kein Spam. Abmeldung jederzeit mit einem Klick möglich.