Komplexe, zugängliche Tabellen
Das lernst du in dieser Lektion
- Du weißt, warum jede Datentabelle eine caption braucht und was sie leisten muss.
- Du setzt scope sicher ein und erkennst, ab wann es nicht mehr ausreicht.
- Du beherrschst die headers/id-Technik für mehrstufige Kopfzeilen.
- Du nutzt colspan, rowspan, colgroup und tfoot gezielt und sparsam.
- Du machst breite Tabellen mit einem barrierefreien Scroll-Container mobiltauglich.
Für Sehende erklärt sich eine Tabelle durch einen Blick – für alle anderen durch ihre Struktur. Wer eine Tabelle sieht, erfasst Zeilen, Spalten und Zusammenhänge in Sekundenbruchteilen. Ein Screenreader dagegen liest Zelle für Zelle vor, und ob dabei „März, Umsatz, 12.400 Euro“ herauskommt oder nur ein zusammenhangloses „12.400“, entscheidet allein dein Markup. In dieser Lektion baust du Tabellen, die beide Welten bedienen: mehrstufige Kopfzeilen, verbundene Zellen, Summenzeilen – und eine responsive Strategie, die auch auf dem Smartphone niemanden aussperrt.
Grundgerüst und caption: das Pflichtprogramm
Zur Erinnerung, kompakt in drei Sätzen: Eine Tabelle lebt in <table>, jede Zeile ist ein <tr>, Datenzellen sind <td> und Kopfzellen <th>. Die Bereiche <thead>, <tbody> und <tfoot> gliedern Kopf, Rumpf und Fuß. Und die eiserne Grundregel gilt weiterhin: Tabellen sind ausschließlich für tabellarische Daten da – nie für Layout.
Zum Pflichtprogramm jeder Datentabelle gehört von nun an ein Element, das in den Grundlagen oft übergangen wird: <caption>. Es steht als erstes Kind direkt in der <table> und benennt, was die Tabelle zeigt. Für Screenreader-Nutzerinnen ist es der entscheidende Kontext: Viele lassen sich zunächst alle Tabellen einer Seite auflisten – und entscheiden anhand der Caption, ob sich das zellenweise Durcharbeiten lohnt. Eine Tabelle ohne Caption ist für sie ein unbeschriftetes Paket. Aber auch sehende Nutzer profitieren, denn eine gute Caption macht die Tabelle unabhängig vom umgebenden Text verständlich.
Formuliere die Caption präzise statt generisch: „Quartalsumsätze 2026 nach Filiale, in Euro“ schlägt „Übersicht“ um Längen. Wenn dein Design keine sichtbare Überschrift vorsieht, blende die Caption per CSS visuell aus (mit einer sogenannten „visually hidden“-Technik) – aber lass sie im Markup stehen. Weglassen ist keine Option.
scope vertieft: col, row, colgroup, rowgroup
Ein <th> sagt nur: „Ich bin eine Kopfzelle.“ Wofür sie Kopf ist – für die Spalte darunter oder die Zeile daneben –, muss der Screenreader sonst raten. Das scope-Attribut beendet das Raten:
scope="col"– diese Kopfzelle gilt für ihre Spalte (der Normalfall in der Kopfzeile).scope="row"– diese Kopfzelle gilt für ihre Zeile (typisch für die erste Zelle jeder Datenzeile).scope="colgroup"/scope="rowgroup"– die Kopfzelle überspannt eine ganze Spalten- bzw. Zeilengruppe, etwa wenn eine übergeordnete Zelle percolspanmehrere Spalten zusammenfasst.
Der Gewinn ist unmittelbar hörbar: Navigiert eine Nutzerin zu einer Zelle, liest der Screenreader die zugehörigen Kopfzellen mit vor – „Filiale Aschaffenburg, März: 12.400“. Ohne scope funktioniert das in einfachen Tabellen oft noch per Heuristik, in komplexeren wird es zuverlässig falsch.
<table>
<caption>Öffnungszeiten der Stadtbücherei Stockstadt</caption>
<thead>
<tr>
<th scope="col">Wochentag</th>
<th scope="col">Vormittag</th>
<th scope="col">Nachmittag</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Dienstag</th>
<td>10–12 Uhr</td>
<td>15–18 Uhr</td>
</tr>
<tr>
<th scope="row">Donnerstag</th>
<td>geschlossen</td>
<td>15–19 Uhr</td>
</tr>
</tbody>
</table>Bis hierher trägt scope problemlos. An seine Grenze kommt es, wenn eine Datenzelle Kopfzellen zugeordnet werden muss, die nicht sauber in ihrer eigenen Spalte oder Zeile stehen – typischerweise bei zwei oder mehr übereinanderliegenden Kopfzeilen-Ebenen, bei denen sich Zuordnungen überkreuzen, oder wenn mitten in der Tabelle Zwischenüberschriften auftauchen. Dann braucht es schwereres Gerät.
headers und id: die Technik für mehrstufige Kopfzeilen
Die headers/id-Technik stellt die Zuordnung explizit her: Jede Kopfzelle bekommt eine eindeutige id, und jede Datenzelle listet in ihrem headers-Attribut die IDs aller Kopfzellen auf, die sie beschreiben – durch Leerzeichen getrennt, von der allgemeinsten zur speziellsten. Das ist Schreibarbeit, aber es ist die einzige Technik, die bei beliebig verschachtelten Köpfen zuverlässig funktioniert. Ein Quartalsbericht mit zwei Kopfebenen zeigt das Muster vollständig:
<table>
<caption>Umsatz und Kosten je Filiale, 1. Halbjahr 2026 (in Tausend Euro)</caption>
<thead>
<tr>
<td rowspan="2"></td>
<th id="q1" colspan="2" scope="colgroup">1. Quartal</th>
<th id="q2" colspan="2" scope="colgroup">2. Quartal</th>
</tr>
<tr>
<th id="q1-umsatz" headers="q1" scope="col">Umsatz</th>
<th id="q1-kosten" headers="q1" scope="col">Kosten</th>
<th id="q2-umsatz" headers="q2" scope="col">Umsatz</th>
<th id="q2-kosten" headers="q2" scope="col">Kosten</th>
</tr>
</thead>
<tbody>
<tr>
<th id="f-ab" scope="row">Aschaffenburg</th>
<td headers="q1 q1-umsatz f-ab">210</td>
<td headers="q1 q1-kosten f-ab">148</td>
<td headers="q2 q2-umsatz f-ab">242</td>
<td headers="q2 q2-kosten f-ab">155</td>
</tr>
<tr>
<th id="f-st" scope="row">Stockstadt</th>
<td headers="q1 q1-umsatz f-st">95</td>
<td headers="q1 q1-kosten f-st">61</td>
<td headers="q2 q2-umsatz f-st">118</td>
<td headers="q2 q2-kosten f-st">64</td>
</tr>
</tbody>
</table>Lies eine beliebige Datenzelle und vollziehe die Kette nach: Die 242 gehört zum 2. Quartal, dort zur Spalte Umsatz, und zur Zeile Aschaffenburg – genau das liest ein Screenreader jetzt vor. Beachte auch die leere Ecke oben links: Sie ist bewusst ein <td> und kein <th>, denn sie ist Kopf von gar nichts.
colspan und rowspan: mächtig, aber sparsam
Mit colspan überspannt eine Zelle mehrere Spalten, mit rowspan mehrere Zeilen – im Quartalsbeispiel oben hast du beides schon im Einsatz gesehen. Zwei Handwerksregeln dazu: Erstens muss die Zellen-Arithmetik aufgehen – jede Zeile muss rechnerisch auf dieselbe Spaltenzahl kommen, sonst rendert der Browser ein schiefes Gitter, ohne sich zu beschweren. Zweitens: Minimiere verbundene Zellen. Jede Verbindung macht die Tabelle schwerer vorlesbar, schwerer per Tastatur navigierbar, schwerer stylbar und schwerer wartbar. Verbundene Zellen im Tabellenkopf sind meist legitim (wie die Quartals-Überschriften); verbundene Zellen im Datenrumpf sind fast immer ein Zeichen, dass eigentlich zwei Tabellen oder eine andere Struktur gemeint waren.
colgroup, tfoot und die Sache mit den Zahlen
Drei Werkzeuge runden den Werkzeugkasten ab. Mit <colgroup> und <col> deklarierst du direkt nach der Caption die Spalten der Tabelle – und kannst ganze Spalten stylen, ohne in jeder Zeile Klassen zu wiederholen: eine Klasse am <col> statt dreißigmal am <td>. (Aus technischen Gründen wirken auf <col> nur wenige CSS-Eigenschaften, vor allem Hintergrund, Rahmen und Breite – für das klassische „Spalte hervorheben“ reicht das völlig.)
Das <tfoot>-Element nimmt Summen- und Ergebniszeilen auf. Der semantische Gewinn: Programme wissen, dass hier keine weiteren Datensätze stehen, sondern eine Zusammenfassung – und beim Druck langer Tabellen kann der Browser Kopf und Fuß auf jeder Seite wiederholen.
Und die Zahlen selbst? Dass Beträge rechtsbündig und am Dezimaltrennzeichen ausgerichtet gehören, ist Aufgabe von CSS (text-align: right und tabellarische Ziffern per font-variant-numeric), nicht von HTML – aber deine sauber deklarierten Spalten machen genau dieses Styling trivial.
<table>
<caption>Vereinskasse 2026: Einnahmen je Sparte, in Euro</caption>
<colgroup>
<col>
<col class="zahlen" span="2">
</colgroup>
<thead>
<tr>
<th scope="col">Sparte</th>
<th scope="col">Beiträge</th>
<th scope="col">Spenden</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Fußball</th>
<td>4.200</td>
<td>860</td>
</tr>
<tr>
<th scope="row">Turnen</th>
<td>2.950</td>
<td>410</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Summe</th>
<td>7.150</td>
<td>1.270</td>
</tr>
</tfoot>
</table>Wann eine Tabelle das falsche Werkzeug ist
Nicht alles, was nach Tabelle aussieht, ist eine. Der klassische Fehlgriff: Schlüssel-Wert-Paare wie ein Steckbrief („Name: …, Gründungsjahr: …, Mitglieder: …“) in eine zweispaltige Tabelle zu pressen. Hier gibt es keine Datensätze, die sich über Zeilen und Spalten vergleichen ließen – es gibt nur Begriffe und ihre Beschreibungen. Genau dafür existiert die Beschreibungsliste: <dl> mit <dt> (Begriff) und <dd> (Beschreibung). Sie ist leichter, responsiv ohne Tricks und semantisch ehrlich. Die Testfrage lautet: Würde eine zweite Datenzeile mit denselben Spalten Sinn ergeben? Wenn nein, ist es keine Tabelle.
Responsive Strategie: der barrierefreie Scroll-Container
Breite Tabellen und schmale Smartphone-Displays vertragen sich nicht – und Tabellen umbrechen wie Fließtext können Browser nicht. Die robusteste Strategie: Die Tabelle behält ihre natürliche Breite und wird in einen horizontal scrollbaren Container gelegt. Damit dieser Container barrierefrei wird, braucht er drei Zutaten: tabindex="0", damit Tastatur-Nutzer ihn fokussieren und mit den Pfeiltasten scrollen können (sonst wäre der abgeschnittene Inhalt für sie unerreichbar); role="region", damit assistive Technologien ihn als benannten Seitenbereich behandeln; und ein aria-label, das dem fokussierten Bereich einen Namen gibt. Fehlt eine der drei Zutaten, ist der Scroll-Container nur für Maus- und Touch-Nutzer gebaut.
Zusammenfassung
- Jede Datentabelle bekommt eine
<caption>– sie ist der Kontext, nach dem Screenreader-Nutzer entscheiden, ob sich die Tabelle lohnt. scope(col,row,colgroup,rowgroup) ordnet Kopfzellen eindeutig zu; bei mehrstufigen, sich überkreuzenden Köpfen übernimmt dieheaders/id-Technik.colspan/rowspanminimieren: Im Kopf oft legitim, im Datenrumpf meist ein Zeichen für die falsche Struktur.colgroup/colstylen ganze Spalten ohne Klassen-Wiederholung,tfootkennzeichnet Summenzeilen; Zahlen-Ausrichtung ist Sache von CSS.- Schlüssel-Wert-Paare gehören in eine
<dl>, nicht in eine Tabelle – Testfrage: Ergäbe eine zweite Datenzeile Sinn? - Responsive Lösung: Scroll-Container mit
tabindex="0",role="region"undaria-label– er erhält die Semantik und bleibt tastaturbedienbar.
In der nächsten Lektion zeigt HTML seine vielleicht unterschätzteste Seite: Interaktion ohne eine einzige Zeile JavaScript. Aufklappbare Bereiche, Dialoge, Popovers und Fortschrittsanzeigen – der Browser hat all das längst eingebaut.
Teste dein Wissen
5 kurze Fragen zu dieser Lektion – die Auswertung passiert direkt im Browser.