Komponenten · Medien & Daten
Komplexe Datentabellen barrierefrei
Eine komplexe Datentabelle wird barrierefrei, wenn jede Datenzelle programmatisch weiß, welche Kopfzellen sie beschreiben: über scope (auch colgroup und rowgroup) bei regelmäßiger Struktur, über headers und id bei mehreren Kopfebenen. Die wirksamste Maßnahme kommt vorher – prüfen, ob sich die Tabelle in mehrere einfache aufteilen lässt.
Eine einfache Tabelle ist schnell erledigt: <th> mit scope für die Kopfzellen, ein <caption> als Überschrift, fertig. Die Grundlagen dazu behandelt Tabellen semantisch aufbauen. Schwierig wird es, wenn eine Tabelle zwei oder mehr Kopfebenen hat – wenn also jede Datenzelle von einer Zeilen- und einer Spaltenüberschrift abhängt, vielleicht noch mit gruppierten Köpfen darüber. Dann reicht scope irgendwann nicht mehr.
Das Wichtigste in Kürze
-
scopeträgt weiter, als viele denken: Nebencolundrowgibt escolgroupundrowgroupfür Köpfe, die über einer ganzen Gruppe von Spalten oder Zeilen stehen. -
headers/idbraucht man erst, wenn Köpfe unregelmäßig verteilt sind, sich innerhalb der Tabelle wiederholen oder ändern – oder wenn drei und mehr Köpfe eine Zelle beschreiben. -
Die Reihenfolge im
headers-Attribut ist die Vorlesereihenfolge. Erst Gruppe, dann Spalte, dann Zeilengruppe und Zeile ergibt einen Satz, der sich anhören lässt. -
Jede
idmuss im Dokument einmalig sein. Ein Tippfehler inheadersfällt visuell nicht auf und macht die Zelle stumm. -
Das HTML-Attribut
summaryan<table>ist seit HTML5 nicht mehr zulässig. Ein erklärender Satz gehört in die<caption>oder in einen Absatz davor. - Verschachtelte Tabellen sind praktisch nie zu retten. Sie sind ein Hinweis auf ein Strukturproblem, nicht auf einen fehlenden Auszeichnungstrick.
- Das W3C empfiehlt im Tabellen-Tutorial ausdrücklich, komplexe Tabellen aufzuteilen, weil das von Redaktionswerkzeugen besser unterstützt wird als
headers/id.
Zuerst: vereinfachen
Meine deutlichste Empfehlung steht vor jeder Technik: Die beste komplexe Tabelle ist eine, die du vereinfachen konntest. Lässt sich eine große Kreuztabelle in zwei kleinere aufteilen – eine je Halbjahr, eine je Standort –, dann würd ich das jedes Mal tun. Zwei einfache Tabellen mit scope sind für alle Beteiligten leichter: für Screenreader-Nutzer, für die Redaktion, für den nächsten Umbau.
Drei Fragen, die dabei helfen:
- Ist wirklich jede Dimension nötig? Oft steckt eine Ebene nur im Layout, weil man Platz sparen wollte.
- Kann eine Dimension zu Überschriften werden? Aus „Spaltengruppe 1. Halbjahr“ wird eine
<h3>, unter der eine eigene Tabelle steht. - Ist die Tabelle überhaupt eine Tabelle? Zwei Spalten mit Begriff und Erklärung sind oft eine Definitionsliste, keine Tabelle.
Erst wenn die Komplexität wirklich im Inhalt steckt, kommen die folgenden Techniken zum Einsatz.
scope für gruppierte Köpfe
Solange die Struktur regelmäßig ist, trägt scope – auch über einfache Zeilen- und Spaltenköpfe hinaus. Mit scope="colgroup" und scope="rowgroup" lassen sich übergeordnete Köpfe kennzeichnen, die für eine ganze Gruppe gelten:
<table>
<caption>Umsatz nach Quartal und Region in Tausend Euro</caption>
<thead>
<tr>
<td></td>
<th scope="colgroup" colspan="2">2025</th>
<th scope="colgroup" colspan="2">2026</th>
</tr>
<tr>
<td></td>
<th scope="col">Q1</th><th scope="col">Q2</th>
<th scope="col">Q1</th><th scope="col">Q2</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Nord</th>
<td>120</td><td>135</td><td>128</td><td>140</td>
</tr>
</tbody>
</table>
Für viele zweidimensionale Tabellen ist das ausreichend und angenehm wartbar. Zwei Details dazu:
Die leere Ecke bleibt ein <td>. Die Zelle oben links, wo Zeilen- und Spaltenköpfe sich kreuzen, hat keinen Inhalt und ist keine Überschrift. Ein leeres <th> dort behauptet eine Kopfzelle, die nichts benennt.
<colgroup> und <col> sind Gestaltungselemente, keine Semantik. Sie eignen sich für Spaltenbreiten und Hintergrundfarben, ersetzen aber scope="colgroup" nicht – ein verbreiteter Irrtum, weil die Namen so ähnlich klingen.
headers und id für echte Mehrdimensionalität
Reicht scope nicht mehr aus, kommt die explizite Variante: Jede Kopfzelle bekommt eine id, und jede Datenzelle verweist per headers auf alle Köpfe, die sie beschreiben. Das ist mehr Schreibarbeit, dafür aber eindeutig.
headers landet man in dieser Tabelle mit einer nackten Zahl.<table>
<caption>Öffnungszeiten nach Standort und Wochentag</caption>
<thead>
<tr>
<td></td>
<th id="mo" scope="col">Montag</th>
<th id="di" scope="col">Dienstag</th>
</tr>
</thead>
<tbody>
<tr>
<th id="berlin" scope="row">Berlin</th>
<td headers="berlin mo">9–18 Uhr</td>
<td headers="berlin di">9–18 Uhr</td>
</tr>
<tr>
<th id="hamburg" scope="row">Hamburg</th>
<td headers="hamburg mo">10–16 Uhr</td>
<td headers="hamburg di">geschlossen</td>
</tr>
</tbody>
</table>
Ein Screenreader liest die Zelle „geschlossen“ jetzt als „Hamburg, Dienstag, geschlossen“ – mit vollem Kontext, egal wo im Tabellengewirr man landet.
Drei Regeln, die den Unterschied zwischen funktionierender und kaputter Auszeichnung machen:
- Die Reihenfolge im Attribut ist die Vorlesereihenfolge.
headers="berlin mo"ergibt „Berlin, Montag“,headers="mo berlin"ergibt „Montag, Berlin“. Beides ist gültig, aber nur eines klingt wie ein Satz. - Kein Mischbetrieb in derselben Tabelle. Entweder
scopeoderheaders/id– beides parallel führt zu widersprüchlichen Zuordnungen, die je nach Screenreader anders aufgelöst werden. - Auch Kopfzellen können Köpfe haben. In mehrstufigen Kopfbereichen verweist ein
<th>der zweiten Ebene perheadersauf den Gruppenkopf darüber. Das W3C weist im Tutorial auf einen Nebeneffekt hin: Wo dafür leere Zellen als Bezugspunkt gebraucht werden, bleiben manche Kopfzellen für einige assistive Technologien ohne eigenen Kopf.
Den Zweck der Tabelle benennen
Jede Datentabelle braucht ein <caption> – es ist die Überschrift der Tabelle und das Erste, was ein Screenreader ankündigt („Tabelle, Öffnungszeiten nach Standort und Wochentag, 3 Spalten, 3 Zeilen“). Bei komplexen Tabellen hilft zusätzlich ein Satz, der die Struktur in Worte fasst:
<caption>
Belegte Plätze nach Halbjahr, Standort und Kursform
<span class="struktur-hinweis">Zeilen: Regionen mit Standorten. Spalten: Halbjahre,
darin je Präsenz und Online.</span>
</caption>
Das nimmt der reinen Tabellennavigation die Orientierungslast: Wer weiß, wie die Tabelle gebaut ist, kann sie gezielt durchlaufen, statt sich Zelle für Zelle ein Bild zu machen.
Wichtig zur Einordnung: Das alte Attribut summary am <table> erfüllte genau diese Aufgabe, ist aber seit HTML5 nicht mehr zulässig und wird vom Validator als Fehler gemeldet. Wer es noch in alten Vorlagen findet, überträgt den Text in die <caption> oder in einen Absatz davor.
So testest du es
Der Test, der die Auszeichnung wirklich prüft, ist die Tabellennavigation eines Screenreaders – nicht der Blick auf den Bildschirm und nicht ein automatischer Prüfer. Automatische Werkzeuge finden fehlende <th> und ungültige headers-Verweise; ob die Zuordnung inhaltlich stimmt, sehen sie nicht.
Mit NVDA unter Windows:
- Tabelle mit
Tanspringen. - Mit
Strg+Alt+ Pfeiltasten von Zelle zu Zelle bewegen. - Auf jeder Zelle prüfen: Werden alle zugehörigen Köpfe genannt, und in einer Reihenfolge, die sich verstehen lässt?
- Gezielt in die Mitte springen und dort dasselbe prüfen. Fehler in
headersfallen selten in der ersten Zeile auf.
Mit VoiceOver auf dem Mac ist der Weg Strg + Alt + Pfeiltasten im Tabellenmodus. Ergänzend zeigt der Accessibility-Inspector der Browser-DevTools, welche Köpfe der Browser einer Zelle zuordnet – das deckt Tippfehler in id-Verweisen schnell auf.
Responsiv, ohne die Struktur zu zerlegen
Breite Tabellen auf schmalen Viewports sind die zweite große Fehlerquelle. Der verbreitete CSS-Trick, jede Zeile per display: block in eine Karte umzubauen und Köpfe per ::before-Inhalt zu wiederholen, zerstört die Tabellensemantik – und damit genau die Zellennavigation, für die man die Tabelle gebaut hat.
Der verlässliche Weg ist ein Scroll-Container:
<div role="region" aria-label="Öffnungszeiten, waagerecht scrollbar" tabindex="0">
<table>…</table>
</div>
Das tabindex="0" macht den Container per Tastatur scrollbar, role="region" plus Name sorgt dafür, dass er als benannter Bereich ansagbar ist. Damit ist auch die Ausnahme der Reflow-Regel korrekt genutzt: Datentabellen dürfen waagerecht scrollen, die Seite drumherum nicht.
Bleibt ein sticky Spaltenkopf im Bild, darf er die fokussierte Zeile nicht verdecken – das verlangt 2.4.11 seit WCAG 2.2.
Häufige Fehler
- Tabellen fürs Layout. Tabellen sind für Daten, nicht für Seitenraster – ein Erbe aus den frühen 2000ern, das niemand zurückbraucht. Wenn es unvermeidbar ist:
role="presentation". -
Datenzellen als
<td>ohne Köpfe. Ohne<th>mitscope(oderheaders) fehlt der Bezug komplett. -
headerszeigt auf eine falsche oder fehlendeid. Die Verweise müssen exakt stimmen, sonst stiftet die Technik mehr Verwirrung als Nutzen. -
scopeundheadersgemischt. Widersprüchliche Zuordnungen je Screenreader. - Verschachtelte Tabellen. Für Screenreader kaum zu durchschauen – fast immer ein Zeichen, dass man vereinfachen sollte.
-
Kein
<caption>. Die Tabelle bleibt namenlos. -
summary-Attribut am<table>. Seit HTML5 ungültig; der Text gehört in die<caption>. -
Leere Ecke als
<th>. Behauptet eine Kopfzelle ohne Inhalt.
Häufige Fragen
scope oder headers/id – was nehme ich?
scope, solange die Struktur regelmäßig ist: Es ist kürzer, besser wartbar und breiter unterstützt. headers/id erst, wenn Köpfe so verteilt sind, dass scope sie nicht mehr eindeutig zuordnen kann – etwa bei drei Kopfebenen oder bei Köpfen, die mitten in der Tabelle wechseln. Beides zu mischen, stiftet Verwirrung.
Wie gehe ich mit sehr breiten Tabellen auf dem Smartphone um?
Lass die Tabelle eine Tabelle bleiben und mache den Bereich waagerecht scrollbar – ein Container mit overflow-x: auto, tabindex="0" und einem Namen. Die Struktur per CSS aufzubrechen zerstört leicht die Zuordnung von Kopf und Zelle, und genau die ist der Zweck der Auszeichnung.
Gehören Diagramme auch hierher?
Ein Diagramm ist ein Bild und braucht eine Textalternative. Oft ist die beste Alternative genau die zugrunde liegende Datentabelle direkt daneben – aufklappbar unter der Grafik. Das Muster dazu steht unter barrierefreie Diagramme.
Was ist mit rowspan und colspan?
Zusammengefasste Zellen sind erlaubt und werden von Screenreadern korrekt gemeldet („Nord, über 2 Zeilen“). Sie machen die Zuordnung aber unübersichtlicher – bei mehr als einer Ebene zusammengefasster Köpfe ist headers/id die sicherere Wahl.
Muss ich <thead>, <tbody> und <tfoot> setzen?
Pflicht sind sie nicht, aber sie helfen: Sie machen die Rollen der Zeilengruppen explizit, ermöglichen scope="rowgroup" sinnvoll und sind die Voraussetzung dafür, dass eine Kopfzeile beim Scrollen sauber sticky bleibt. Eine Summenzeile gehört in <tfoot>.
Fazit
Komplexe Tabellen werden zugänglich, indem jede Datenzelle ihre Köpfe kennt: über scope (auch colgroup/rowgroup) bei regelmäßiger Struktur, über headers/id bei echter Mehrdimensionalität – immer mit einem benennenden <caption> und, wo es hilft, einem Satz zur Struktur.
Die wirkungsvollste Maßnahme bleibt aber, die Tabelle so einfach zu halten, wie der Inhalt es zulässt. Und der einzige Test, der wirklich zählt, ist die Tabellennavigation im Screenreader: Wer sich mit Strg + Alt + Pfeiltasten durch die Mitte der Tabelle bewegt und dabei immer weiß, wo er ist, hat es richtig gemacht. Sobald Nutzer die Daten sortieren oder filtern sollen, geht es weiter im Muster sortierbare & filterbare Tabellen.
Ein Nebeneffekt, der die Arbeit zusätzlich rechtfertigt: Genau diese Auszeichnung macht eine Tabelle auch für Suchmaschinen und KI-Systeme auswertbar. Eine Tabelle mit caption, th und scope ist eine in sich verständliche Einheit – und damit die zitierfähigste Form, in der sich Werte veröffentlichen lassen, wie GEO-Grundlagen zeigt.
Quellen
- Tables with Multi-Level Headers (W3C WAI – headers/id im Detail, inklusive Empfehlung zur Aufteilung)
- The th element: scope (HTML Living Standard – die vier zulässigen scope-Werte und ihre Wirkung)