Tabellen/Responsive Gestaltung
Tabellen enthalten zweidimensionale strukturierte Daten:
Zeilen × Spalten.
Responsive Design funktioniert normalerweise mit Inhalten, die man neu anordnen, umbrechen, ausblenden oder stapeln kann, aber die Semantik von Tabellen erfordert, dass das Raster aussagekräftig bleibt.
Also:
- Text kann umgebrochen werden
- Spalten können nicht umgebrochen werden
- Kopfzeilen müssen mit den Zellen verbunden bleiben
- Benutzer müssen weiterhin die tabellarischen Beziehungen verstehen können
Dieses Tutorial zeigt, wie man Tabellen so anzeigen kann, dass man auch auf schmalen Viewpworts zu den Inhalten mit Scrollen gelangen kann, bzw. wie man Inhalte so verteilt, dass sie auf den verfügbaren Raum passen.
Inhaltsverzeichnis
Tabelle mit Scroll Snap und Sticky Header
Eine Tabelle mit Scrollbalken darzustellen, ist das sicherste Muster: Der gesamte Inhalt bleibt verfügbar, und Nutzer können sowohl vertikal als auch horizontal scrollen.
Damit Nutzer merken, dass es außerhalb des Viewports weitere Inhalte gibt, sollen die Scrollbalken extra gekennzeichnet werden. Damit die Übersicht erhalten bleibt, soll der Tabellenkopf durch das im letzten Kapitel beschriebene position: sticky aber auch auf schmalen Viewports erreichbar bleiben.
table {
border-collapse: collapse;
}
table caption {
position: sticky;
left: 0;
max-width: 100vw;
}
/* 1. Zeile sticky */
thead {
position: sticky;
top: 0;
z-index: 3;
}
/* 1. Spalte sticky */
td:first-child, th:first-child {
position: sticky;
left: 0;
z-index: 2;
}
Diese Vorgehensweise hat viele Vorteile:
- Behält die echte Tabellensemantik bei, was für Screenreader wichtig ist.
- Kein Risiko, dass die Ausrichtung beschädigt wird
- Funktioniert mit jeder Tabelle – egal ob groß, komplex, mit Spalten- oder Zeilenüberschneidungen
Leider gibt es eben die typischen Nachteile
- Horizontal scrollende Oberflächen sind auf Mobilgeräten nicht ideal
- Die Tabelle wird nicht verkleinert, sondern nur das Verschieben ermöglicht.
Kann unschön aussehen, wenn Ihr Design „keine Bildlaufleisten” betont
Verwende dies, wenn die Daten in einem echten Raster bleiben müssen (Finanzdaten, Protokolle, Zeitpläne).
Gestapelte Tabelle
In dieser Variante wird jede Zeile wird zu einem Block. Die Überschriften erscheinen mit einem data-label-Attribut als Beschriftungen vor jeder Zelle. Auf diese Weise „kennt“ jede Zelle ihre Spaltenüberschrift, wenn die Tabelle auf kleinen Bildschirmen gestapelt wird.
<tr>
<td data-label="Abk.">AG</td>
<td data-label="Kanton"><img alt="" src="Wappen_Aargau_matt.svg" width="20" height="24">Aargau</td>
<td data-label="Kantons-nummer">19</td>
<td data-label="Beitritt">1803</td>
<td data-label="Hauptort">Aarau</td>
<td data-label="Einwohner31. Dezember 2023">726'894</td>
<td data-label="Fläche(km²)">1403.80</td>
<td data-label="MitgliederStänderat">2</td>
<td data-label="Einwohner-dichte">518</td>
<td data-label="Amtssprache">Deutsch</td>
</tr>
Mit data-Attributen (Custom Data Attributes) hast du die Möglichkeit, Elementen eigene Attribute mitzugeben, die dann per Script ausgewertet oder mit CSS genutzt werden können:
@media (width < 40em) {
@media (width < 40em) {
table, thead, tbody, th, td, tr {
display: block;
}
thead {
display: none;
}
td {
display: flex;
justify-content: space-between;
padding-left: 50%;
position: relative;
}
td::before {
content: attr(data-label);
font-weight: bold;
position: absolute;
left: 1rem;
}
}
Bis zu einer Viewportbreite von 40em werden alle Tabellenzellen untereinander dargestellt und dafür der Inhalt des data-header-Attributs als Pseudoelement vorangestellt. Über die attr()-Funktion wird der Wert des data-header ausgelesen und mit content dem Pseudoelement als Inhalt übergeben.
Dies wird durch die Medienabfrage @media (width < 40em) { ...} erreicht. Anders als bei Mobile First! ist es hier sinnvoll, die „normale“ Tabelle zuerst zu formatieren und dann das CSS für die mobile Variante in der media query festzulegen.
Vorteil ist die leichte Lesbarkeit von breiten Tabellen auf Mobiltelefonen. Die Nutzer lesen Inhalte eher auf ihren Handys, als dass sie Daten vergleichen.
Bei sehr großen oder mit colspans/rowspans verschachtelten Tabellen ist dieser Ansatz sinnlos.
Auch zum Vergleichen von Werten ist dieser Ansatz ungeeignet.
Bei großen Tabellen ist es empfehlenswert, die data-header durch ein Script zu setzen:
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('table').forEach(table => {
const headers = Array.from(
table.querySelectorAll('thead th')
).map(th => th.textContent.trim());
table.querySelectorAll('tbody tr').forEach(row => {
row.querySelectorAll('td').forEach((td, index) => {
if (headers[index]) {
td.setAttribute('data-label', headers[index]);
}
});
});
});
});
Mit forEach werden für jede Tabelle alle Texte der th-Elemente ausgelesen, dann in jeder Zeile in <tbody> jedem <td> mithilfe setAttribute eine Datenbeschriftung zugewiesen.
Container Queries
In dieser Version nutzen wir anstelle einer Medienabfrage eine Container Query, die nur die Größe des Elternelements abfragt:
.table-container {
container: card / inline-size;
width: 35em; /* Nur zum Testen: Verringern Sie den Wert auf <30em! */
}
@container (max-width: 30em) {
table {
display: block;
border-collapse: collapse;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
tr, th, td {
display: block;
}
tr {
padding: 1em;
border-top: 0 none;
}
th {
padding: 0;
}
td {
padding: 1em 0 0;
}
}
Die Tabelle erhält nun ein wrapper-Element, dass mit container: card / inline-size; einen containment-context für dieses Eltern-Element erstellt.
Wir machen die Elemente der Tabelle allesamt zu Block-Elementen, während die Eigenschaft width: 100% sicherstellt, dass sie die volle Breite des enthaltenen Elements einnehmen. So werden sie vertikal gestapelt.
Die Eigenschaften overflow-x: auto sorgt dafür, dass die Tabelle auf Touch-Geräten horizontal gescrollt werden kann. Dazu sortieren wir die Innenabstände ein wenig um und entfernen nun doppelte Rahmenlinien, weil border-collapse nur auf Elemente wirkt, deren display-Eigenschaft ein table im Namen hat.
Spalten ausblenden
In bestimmten Fällen kann es sinnvoll sein, Spalten mit niedrigerer Priorität bei geringer Breite auszublenden. Dabei gibt es verschiedene Ansätze, die alle ihre Nachteile haben:
mit CSS
Mit CSS kann man Elemente an Hand der Position in ihrem Elternelement auswählen:
@media (width < 40em) {
table tr > :nth-child(1),
table tr > :nth-child(3),
table tr > :nth-child(4),
table tr > :nth-child(8),
table tr > :nth-child(9) {
display: none;
}
}
Aus jeder Tabellenzeile werden mit nth-child() die 1., 3., 4., 8. und 9. Spalten entfernt.
Diese Methode hat zwei große Nachteile:
- Du entscheidest, was Benutzer nicht sehen können. Der Nutzer hat keine Kontrolle, was er sehen darf.
- Es kann die semantische Bedeutung zerstören, wenn die falsche Spalte verschwindet.
So etwas kann passieren, wenn man die Tabelle erweitert, dabei das vorhandene CSS aber nicht anpasst.
Verwende diese Option nur, wenn du die ausgeblendeten Spalten dokumentierst oder einen Link „Vollständige Tabelle“ bereitstellst.
mit HTML
Man könnte den Tabellenzellen, die ausgeblendet werden sollen, eine Klasse hinzufügen. Das kann entwender manuell oder dynamisch erfolgen.
<table>
<tr>
<td class="is-hidden"></td>
<td></td>
<td class="is-hidden"></td>
<td class="is-hidden"></td>
<td></td>
<td></td>
<td></td>
<td class="is-hidden"></td>
<td class="is-hidden"></td>
</tr>
…
</table>
Im CSS wird diese Klasse mit display: none wieder ausgeblendet:
@media (width < 40em) {
.is-hidden { display: none; }
}
Wie oben erwähnt, überwiegen hier aber auch die Nachteile, sodass eigentlich die erste Version mit dem sticky header allen anderen überlegen ist!
Siehe auch
- E-Mail-Newsletter

E-Mails und Newsletter mit HTML und CSS gestalten
Hier ist Tabellen-Layout noch nötig! - interaktive Landkarten

- Choroplethenkarte
- Gestaltung mit CSS
- Hintergründe in Tabellen
- Zebrastreifen
- Monatskalender

Tabellen dynamisch erzeugen
Weblinks
- Responsive, Accessible Table Experiments von Adrian Roselli