Listen und Tabellen barrierefrei auszeichnen
A11Y
WCAG
Barrierefreiheit · WCAG · Semantisches HTML · Screenreader
Listen und Tabellen barrierefrei auszeichnen
ul, ol, dl und th mit scope statt div-Suppe

Wer Listen als gestapelte div-Elemente und Tabellen als reines Layoutwerkzeug missbraucht, macht Inhalte für Screenreader-Nutzer unbrauchbar. Mit ul, ol, dl für echte Aufzählungen und th mit scope sowie caption für echte Tabellendaten werden Beziehungen zwischen Inhalten programmatisch erkennbar, navigierbar und WCAG-konform, ohne visuelles Design zu opfern.

14 Min. Lesezeit ul · ol · dl · th scope · caption WCAG 2.2 · Magento 2 · Hyvä Theme

1. Warum semantische Auszeichnung über Zugänglichkeit entscheidet

Screenreader und andere assistive Technologien verlassen sich nicht auf visuelles Design, sondern auf die semantische Struktur des HTML-Dokuments. Eine Liste, die nur durch Zeilenumbrüche und CSS-Abstände wie eine Liste aussieht, ist für einen Screenreader schlicht eine Ansammlung unzusammenhängender div-Elemente. Erst <ul>, <ol> oder <dl> teilen der Accessibility-API mit, dass hier eine Gruppe zusammengehöriger Elemente vorliegt, die als Einheit navigiert werden kann. Genau diese programmatisch erkennbare Struktur fordern die WCAG-Erfolgskriterien 1.3.1 (Info and Relationships) und 4.1.2 (Name, Role, Value) explizit ein.

Der Unterschied ist für sehende Nutzer unsichtbar, für Screenreader-Nutzer aber fundamental: NVDA kündigt eine korrekt ausgezeichnete Liste mit „Liste mit 8 Einträgen" an und erlaubt die Navigation von Eintrag zu Eintrag per Taste. Bei einer div-Konstruktion fällt diese Ankündigung komplett weg, und der Nutzer muss sich Element für Element durch unstrukturierten Text vorarbeiten, ohne zu wissen, wie viele Einträge noch folgen oder wo eine logische Gruppe endet. Bei Tabellen gilt dasselbe Prinzip in verschärfter Form: Ohne <th scope> kann ein Screenreader eine Zelle nicht mehr ihrer Zeilen- oder Spaltenüberschrift zuordnen, und komplexe Datentabellen werden faktisch unlesbar.

2. ul, ol und dl: die drei Listentypen von HTML

HTML bietet drei native Listentypen, die jeweils eine unterschiedliche semantische Aussage treffen. <ul> (unordered list) signalisiert, dass die Reihenfolge der Einträge keine inhaltliche Bedeutung hat, etwa bei einer Liste von Produktmerkmalen oder Navigationslinks. <ol> (ordered list) hingegen zeigt an, dass die Reihenfolge relevant ist, zum Beispiel bei Schritt-für-Schritt-Anleitungen im Checkout-Prozess oder einer Rangliste. <dl> (description list) bildet Schlüssel-Wert-Paare ab und eignet sich hervorragend für Produktattribute wie Material, Größe und Gewicht, die als <dt>/<dd>-Paare ausgezeichnet werden.

Die Wahl des richtigen Listentyps ist keine Stilfrage, sondern beeinflusst direkt, was ein Screenreader ankündigt. VoiceOver liest bei einer <ol> automatisch die Positionsangabe „Eintrag 3 von 7" vor, was bei einer nummerierten Anleitung wichtige Orientierung gibt. Bei einer <ul> entfällt diese Positionsangabe zugunsten der reinen Ankündigung „Listeneintrag". Wichtig: Jedes direkte Kind von <ul> oder <ol> muss ein <li>-Element sein, alles andere verletzt die HTML-Spezifikation und führt in der Accessibility-Tree-Berechnung zu inkonsistentem Verhalten zwischen Browsern.


<!-- ul: Reihenfolge irrelevant, e.g. product features -->
<ul class="space-y-2">
  <li>Wasserdicht bis 30 Meter</li>
  <li>Titan-Gehäuse, 42 mm Durchmesser</li>
  <li>Saphirglas kratzfest</li>
</ul>

<!-- ol: Reihenfolge relevant, e.g. checkout steps -->
<ol class="list-decimal pl-6 space-y-1">
  <li>Warenkorb prüfen</li>
  <li>Versandadresse eingeben</li>
  <li>Zahlungsart wählen</li>
  <li>Bestellung bestätigen</li>
</ol>

<!-- dl: key-value pairs, e.g. product attributes -->
<dl class="grid grid-cols-[max-content_1fr] gap-x-4 gap-y-1">
  <dt class="font-semibold">Material</dt>
  <dd>Titan, gebürstet</dd>
  <dt class="font-semibold">Gewicht</dt>
  <dd>68 g</dd>
  <dt class="font-semibold">Wasserdichtigkeit</dt>
  <dd>30 m (3 ATM)</dd>
</dl>

3. Das Problem mit div-Suppe statt echter Listen

„Div-Suppe" bezeichnet den weitverbreiteten Anti-Pattern, listenartige Inhalte ausschließlich mit verschachtelten <div>-Elementen und CSS-Klassen wie flex flex-col gap-2 nachzubauen. Visuell ist das Ergebnis von einer echten Liste nicht zu unterscheiden, weil Tailwind-Utility-Klassen dieselben Abstände und Ausrichtungen erzeugen können wie native Listenelemente. Für die Accessibility-API existiert der semantische Zusammenhang aber schlicht nicht: Der Accessibility Tree meldet lediglich eine generische „group"-Rolle oder gar keine Rolle, und die Anzahl der Einträge bleibt dem Nutzer verborgen.

Dieses Muster entsteht in der Praxis oft schleichend: Ein Entwickler kopiert eine bestehende Komponente, die zufällig divs statt li verwendet, und multipliziert das Problem über weitere Komponenten hinweg. Automatisierte Tools wie axe-core oder Lighthouse erkennen div-Suppe nicht zuverlässig als Fehler, weil technisch kein WCAG-Kriterium direkt verletzt wird, solange keine ARIA-Rolle falsch gesetzt ist. Genau deshalb ist manuelles Review mit Screenreader unverzichtbar. Die Regel ist einfach: Sobald Inhalte eine Aufzählung, eine Reihenfolge oder eine Gruppe zusammengehöriger Einträge darstellen, gehört ein natives Listenelement dorthin, unabhängig vom visuellen Styling.


<!-- WRONG: div soup, no semantic list -->
<div class="flex flex-col gap-2">
  <div class="border-b py-2">Versandkostenfrei ab 50 EUR</div>
  <div class="border-b py-2">2 Jahre Garantie</div>
  <div class="border-b py-2">30 Tage Rückgaberecht</div>
</div>

<!-- RIGHT: semantic list with identical visual result -->
<ul class="flex flex-col gap-2 list-none m-0 p-0">
  <li class="border-b py-2">Versandkostenfrei ab 50 EUR</li>
  <li class="border-b py-2">2 Jahre Garantie</li>
  <li class="border-b py-2">30 Tage Rückgaberecht</li>
</ul>

Navigationsmenüs, Warenkorb-Positionen und Produktraster sind die drei häufigsten Stellen in Magento- und Hyvä-Shops, an denen echte Listen fälschlicherweise als div-Konstrukte umgesetzt werden. Eine Hauptnavigation gehört immer in <nav><ul><li><a>, damit ein Screenreader-Nutzer per Tastenkürzel direkt zur Liste der Navigationspunkte springen und deren Anzahl erfassen kann. Bei Warenkorb-Positionen signalisiert eine <ul>, dass mehrere gleichartige Einheiten vorliegen, was bei aria-live-Ankündigungen nach dem Hinzufügen eines Artikels ebenfalls präziser funktioniert.

Produktraster (Product Grids) sind ein Grenzfall: Eine Sammlung von Produktkarten ist inhaltlich eine Liste, wird aber oft mit CSS Grid statt Flexbox realisiert. Das ist kein Widerspruch, denn display: grid lässt sich problemlos auf <ul> und <li> anwenden, ohne dass CSS die zugrunde liegende Semantik verändert. In Hyvä-Themes mit Tailwind CSS bedeutet das konkret: Die Grid-Klassen wandern auf das <ul>-Element, während jede Produktkarte in einem <li> steckt, das selbst wieder list-none erhält, um die Browser-Standardaufzählungszeichen zu entfernen, ohne die Semantik zu verlieren.


<!-- Hyvä phtml: product grid as semantic list with CSS grid -->
<nav aria-label="{{ __('Hauptnavigation') }}">
  <ul class="flex gap-6 list-none m-0 p-0">
    <li><a href="{{ $categoryUrl }}" class="hover:underline">Uhren</a></li>
    <li><a href="{{ $categoryUrl2 }}" class="hover:underline">Schmuck</a></li>
  </ul>
</nav>

<ul class="grid grid-cols-2 sm:grid-cols-4 gap-6 list-none m-0 p-0" aria-label="{{ __('Produkte') }}">
  <?php foreach ($products as $product): ?>
    <li>
      <a href="{{ $product->getProductUrl() }}" class="block">
        <img src="{{ $product->getImageUrl() }}" alt="{{ $escaper->escapeHtmlAttr($product->getName()) }}" width="300" height="300">
        <span class="block mt-2 font-semibold"><?= $escaper->escapeHtml($product->getName()) ?></span>
      </a>
    </li>
  <?php endforeach; ?>
</ul>

5. Tabellen-Grundlagen: th, scope und caption

Eine <table> ist für echte tabellarische Daten gedacht: Zeilen und Spalten, die in einer zweidimensionalen Beziehung zueinander stehen, etwa eine Preisliste mit Größen in den Spalten und Produkten in den Zeilen. Das Attribut scope="col" auf einem <th>-Element erklärt, dass diese Zelle als Überschrift für die gesamte Spalte gilt, während scope="row" dasselbe für eine Zeile signalisiert. Ohne scope muss ein Screenreader raten, welche Überschrift zu welcher Datenzelle gehört, was bei mehr als zwei Spalten schnell zu Fehlzuordnungen führt.

Das <caption>-Element, direkt nach dem öffnenden <table>-Tag platziert, liefert einen kurzen, beschreibenden Titel, der von Screenreadern automatisch vorgelesen wird, bevor der Nutzer in die eigentlichen Tabellendaten eintaucht. Das ersetzt Konstruktionen mit einer separaten Überschrift außerhalb der Tabelle, die programmatisch nicht mit der Tabelle verknüpft ist. Zusammen mit <thead>, <tbody> und korrekt gesetztem scope entsteht eine Tabelle, die ein Screenreader Zelle für Zelle mit vollständigem Kontext vorlesen kann, ohne dass der Nutzer selbst mitzählen muss, in welcher Spalte er sich befindet.


<!-- Simple data table with caption and scope -->
<table class="w-full text-sm border-collapse">
  <caption class="text-left font-semibold mb-2">Verfügbare Größen und Preise</caption>
  <thead>
    <tr>
      <th scope="col">Größe</th>
      <th scope="col">Farbe</th>
      <th scope="col">Preis</th>
      <th scope="col">Verfügbar</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">S</th>
      <td>Schwarz</td>
      <td>49,90 EUR</td>
      <td>Ja</td>
    </tr>
    <tr>
      <th scope="row">M</th>
      <td>Schwarz</td>
      <td>49,90 EUR</td>
      <td>Nein</td>
    </tr>
  </tbody>
</table>

6. Komplexe Tabellen: headers, id und mehrstufige Header

Sobald eine Tabelle mehrstufige Überschriften hat, etwa eine gruppierte Kopfzeile mit „Q1" und „Q2", die sich jeweils in „Umsatz" und „Stückzahl" aufteilt, reicht scope allein nicht mehr aus, weil eine Datenzelle dann zu mehr als einer Überschrift gleichzeitig gehört. Für diese Fälle definiert HTML das headers-Attribut: Jede <th>-Zelle bekommt eine eindeutige id, und jede <td>-Zelle referenziert per headers="id1 id2" alle zutreffenden Überschriften, getrennt durch Leerzeichen. Screenreader lesen dann bei jeder Datenzelle die vollständige Kette aller zugehörigen Überschriften vor, bevor der eigentliche Wert folgt.

In der Praxis lohnt sich diese Komplexität fast nie für Standard-Shopdaten. Bevor eine komplexe verschachtelte Tabelle gebaut wird, lohnt sich die Frage, ob sich die Daten nicht in zwei einfachere, klar getrennte Tabellen aufteilen lassen. Einfache Tabellen mit scope="col" und scope="row" sind für Screenreader-Nutzer erheblich leichter zu erfassen als eine Tabelle mit drei Ebenen verschachtelter Header, selbst wenn headers technisch korrekt gesetzt ist. Komplexität in der Datenstruktur sollte, wo möglich, in der Präsentation reduziert werden, nicht nur technisch korrekt ausgezeichnet.


<!-- Multi-level table: headers attribute for unambiguous mapping -->
<table class="w-full text-sm border-collapse">
  <caption class="text-left font-semibold mb-2">Umsatz und Stückzahl nach Quartal</caption>
  <thead>
    <tr>
      <th id="produkt" rowspan="2" scope="col">Produkt</th>
      <th id="q1" colspan="2" scope="colgroup">Q1</th>
      <th id="q2" colspan="2" scope="colgroup">Q2</th>
    </tr>
    <tr>
      <th id="q1-umsatz" headers="q1" scope="col">Umsatz</th>
      <th id="q1-stueck" headers="q1" scope="col">Stückzahl</th>
      <th id="q2-umsatz" headers="q2" scope="col">Umsatz</th>
      <th id="q2-stueck" headers="q2" scope="col">Stückzahl</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th id="uhr-a" scope="row">Uhr A</th>
      <td headers="uhr-a q1 q1-umsatz">12.400 EUR</td>
      <td headers="uhr-a q1 q1-stueck">248</td>
      <td headers="uhr-a q2 q2-umsatz">15.900 EUR</td>
      <td headers="uhr-a q2 q2-stueck">318</td>
    </tr>
  </tbody>
</table>

7. Warum Tabellen niemals für visuelles Layout dienen sollten

In den frühen 2000er-Jahren war die Layout-Tabelle Standardpraxis, um mehrspaltige Webseiten zu bauen, bevor CSS Grid und Flexbox verfügbar waren. Dieses Muster ist heute nicht nur technisch überholt, sondern aktiv schädlich für die Zugänglichkeit: Ein Screenreader interpretiert jede <table> als tabellarische Daten und kündigt Zeilen- und Spaltenzahlen an, selbst wenn die Tabelle nur zur visuellen Anordnung von Logo, Navigation und Inhalt dient. Der Nutzer hört dann sinnlose Ansagen wie „Tabelle mit 3 Spalten und 1 Zeile", die keinerlei inhaltlichen Mehrwert bieten und die Navigation aktiv erschweren.

Der einzige zulässige Ausweg, wenn aus Legacy-Gründen dennoch eine Tabelle für Layout verwendet werden muss, ist role="presentation" auf dem <table>-Element, was die tabellarische Semantik für die Accessibility-API vollständig entfernt. Diese Lösung ist aber ein Notbehelf und keine Empfehlung, denn moderne CSS-Layout-Mechanismen wie Grid und Flexbox lösen dasselbe visuelle Problem ohne semantische Nebenwirkungen. Die klare Regel lautet: <table> ausschließlich für Daten, die tatsächlich in einer Zeilen-Spalten-Beziehung stehen, niemals für die Anordnung von Seitenelementen.

8. Screenreader-Test: wie NVDA und VoiceOver Listen und Tabellen vorlesen

Der zuverlässigste Weg, semantische Auszeichnung zu prüfen, ist der direkte Test mit einem Screenreader, weil automatisierte Linter wie axe-core nur einen Teil der möglichen Fehler erkennen. Mit NVDA (Windows, kostenlos) navigiert man per L-Taste durch alle Überschriften und per Insert+F7 durch eine Elementliste, die alle Listen und Tabellen einer Seite mit ihrer jeweiligen Größe anzeigt. Eine korrekt ausgezeichnete <ul> mit fünf Einträgen wird dabei explizit als „Liste mit 5 Einträgen" geführt, eine div-Suppe erscheint dort gar nicht erst.

VoiceOver auf macOS und iOS bietet mit dem Rotor (Ctrl+Option+U) eine vergleichbare Übersicht über Tabellen und Listen. Bei Tabellen navigiert VoiceOver mit Ctrl+Option+Pfeiltasten zellenweise und liest bei jedem Wechsel automatisch die zugehörige Spalten- und Zeilenüberschrift vor, vorausgesetzt scope ist korrekt gesetzt. Ein praktischer Testablauf für jede neue Komponente: Tab-Navigation ohne Maus durchführen, Elementliste prüfen, und bei Tabellen gezielt in die mittleren Zellen springen, um zu verifizieren, dass die Überschriften-Ansage stimmt und nicht nur bei der ersten Spalte zufällig korrekt klingt.

Als Ergänzung zum manuellen Screenreader-Test lässt sich axe-core in die CI-Pipeline integrieren und gezielt auf die Regeln th-has-data-cells, td-headers-attr, list und listitem beschränken. Solche automatisierten Checks erkennen fehlende th- und scope-Zuordnungen zuverlässig, ersetzen aber, wie im vorherigen Abschnitt beschrieben, niemals den manuellen Test mit einem echten Screenreader.

9. Praxisbeispiel: barrierefreie Produktvergleichstabelle

Produktvergleichstabellen sind ein häufiges E-Commerce-Feature und gleichzeitig ein Musterbeispiel für echte tabellarische Daten: Mehrere Produkte in Spalten, mehrere Merkmale in Zeilen, klare Zeilen- und Spaltenbeziehungen. Die Umsetzung mit <caption>, <th scope="col"> für die Produktnamen in der Kopfzeile und <th scope="row"> für die Merkmalsbezeichnung in der ersten Spalte jeder Zeile erfüllt WCAG 1.3.1 vollständig, ohne dass zusätzliche ARIA-Attribute nötig sind. Native HTML-Semantik ist hier bewusst die erste Wahl vor ARIA, denn die „No ARIA is better than Bad ARIA"-Regel gilt auch für Tabellen.

Wichtig bei der visuellen Gestaltung mit Tailwind CSS ist, dass Zebra-Streifen und Farbcodierung wie „grün = verfügbar" niemals die einzige Information tragen dürfen. Ein Screenreader liest keine Hintergrundfarbe vor, weshalb zusätzlicher Text wie „Verfügbar" oder ein <span class="sr-only"> mit dem Status nötig ist. Die folgende Tabelle zeigt eine vollständig barrierefreie Umsetzung inklusive Falsch-Richtig-Vergleich der häufigsten Fehler bei Listen und Tabellen in der Praxis.

Vergleich unzugänglicher und barrierefreier Umsetzungen von Listen und Tabellen
Anwendungsfall Unzugänglich Barrierefrei Warum wichtig
Merkmalsliste div.flex.flex-col ul>li Anzahl der Einträge wird angesagt
Checkout-Schritte div mit Nummern als Text ol>li Position "3 von 5" automatisch
Produktattribute div mit Doppelpunkt-Text dl>dt/dd Schlüssel-Wert-Beziehung erkennbar
Vergleichstabelle td statt th in Kopfzeile th scope="col" Spaltenbezug jeder Zelle klar
Verfügbarkeit nur grüner Punkt per CSS Text + sr-only-Status Farbe allein transportiert nichts
Seitenlayout table für Spaltenanordnung CSS Grid / Flexbox Keine sinnlose Tabellenansage

Mironsoft

Barrierefreiheit, semantisches HTML und WCAG-Audits für Magento-Shops

Listen und Tabellen wirklich barrierefrei umsetzen?

Wir prüfen bestehende Magento- und Hyvä-Templates auf div-Suppe, fehlende scope-Attribute und Layout-Tabellen und ersetzen sie durch semantisch korrekte, WCAG-konforme Auszeichnung, ohne visuelles Design zu verändern.

Accessibility-Audit

Manuelle Screenreader-Prüfung plus axe-core-Analyse aller Templates

Template-Refactoring

div-Suppe durch ul/ol/dl ersetzen, th und scope in Tabellen nachrüsten

WCAG-Schulung

Praxis-Workshop für euer Team zu semantischem HTML und ARIA

10. Zusammenfassung

Die semantische Auszeichnung von Listen und Tabellen entscheidet direkt darüber, ob Inhalte für Screenreader-Nutzer nutzbar sind. <ul>, <ol> und <dl> ersetzen div-Konstrukte für Aufzählungen, Reihenfolgen und Schlüssel-Wert-Paare und liefern automatisch Positions- und Mengenangaben, die assistive Technologien vorlesen. <th scope="col">, <th scope="row"> und <caption> machen echte Datentabellen navigierbar, während das headers-Attribut für mehrstufige Header nötig wird, wenn scope allein nicht mehr ausreicht.

Tabellen für visuelles Layout sind seit der Einführung von CSS Grid und Flexbox obsolet und erzeugen bei jedem Seitenaufruf sinnlose Screenreader-Ansagen. Der zuverlässigste Weg, semantische Fehler zu finden, bleibt der manuelle Test mit NVDA oder VoiceOver, ergänzt durch automatisierte axe-core-Checks in der CI-Pipeline. Wer diese Grundregeln konsequent in Magento- und Hyvä-Templates anwendet, macht Produktlisten, Navigationsmenüs und Vergleichstabellen für alle Nutzergruppen gleichermaßen zugänglich.

Listen und Tabellen barrierefrei auszeichnen: Das Wichtigste auf einen Blick

Listentyp wählen

ul ohne Reihenfolge, ol mit Reihenfolge, dl für Schlüssel-Wert-Paare. Nie div-Suppe für echte Aufzählungen.

th mit scope

scope="col" und scope="row" auf jedem Header ordnen Datenzellen eindeutig zu.

caption statt Überschrift

caption im table-Element wird von Screenreadern automatisch vorgelesen, bevor Daten folgen.

Kein Layout mit table

CSS Grid und Flexbox statt Layout-Tabellen. Screenreader kündigen sonst sinnlose Zeilen/Spalten an.

11. FAQ: Listen und Tabellen barrierefrei auszeichnen

1Warum reicht CSS-Styling nicht aus, um eine Liste wie eine Liste aussehen zu lassen?
CSS verändert nur das Aussehen, nicht die Semantik. Ein Screenreader erkennt eine div-Konstruktion nicht als Liste und kündigt weder Anzahl noch Navigation an.
2Wann verwende ich ul, wann ol und wann dl?
ul ohne bedeutsame Reihenfolge, ol mit relevanter Reihenfolge, dl für Schlüssel-Wert-Paare mit dt/dd.
3Was macht das scope-Attribut auf einem th-Element?
scope=col gilt für die Spalte, scope=row für die Zeile. Damit ordnet der Screenreader jeder Datenzelle die richtige Überschrift zu.
4Wozu dient das caption-Element in einer Tabelle?
Liefert einen mit der Tabelle programmatisch verknüpften Titel, der vor den Daten automatisch vorgelesen wird.
5Wann brauche ich das headers-Attribut statt scope?
Bei mehrstufigen Kopfzeilen, wenn eine Zelle zu mehreren Überschriften gehört. th bekommt id, td referenziert per headers.
6Warum sollten Tabellen niemals für visuelles Layout verwendet werden?
Screenreader kündigen bei jeder table Zeilen und Spalten an, was bei reinem Layout sinnlos ist. CSS Grid/Flexbox lösen das ohne semantische Nebenwirkung.
7Wie teste ich, ob eine Liste oder Tabelle korrekt ausgezeichnet ist?
Mit NVDA (Insert+F7 Elementliste) oder VoiceOver (Rotor). Automatisierte Tools wie axe-core ergänzen nur, ersetzen den manuellen Test nicht.
8Reicht eine grüne Farbe aus, um Verfügbarkeit anzuzeigen?
Nein. Farbe wird von Screenreadern nicht vorgelesen. Zusätzlicher Text oder sr-only-Status ist zwingend erforderlich.
9Muss jedes Kind eines ul oder ol ein li-Element sein?
Ja, HTML-Spezifikation. Andere direkte Kinder führen zu inkonsistentem Verhalten im Accessibility Tree verschiedener Browser.
10Erkennen automatisierte Tools wie axe-core div-Suppe zuverlässig?
Nur teilweise, weil kein WCAG-Kriterium technisch direkt verletzt wird. Manuelles Screenreader-Review bleibt unverzichtbar.