Große Datentabellen werden auf kleinen Bildschirmen oft unleserlich, da Spalten überlaufen oder so klein werden, dass sie nicht mehr lesbar sind. In diesem Leitfaden wird gezeigt, wie Sie die Sticky-Positionierung verwenden, damit Kopfzeilen beim Scrollen sichtbar bleiben, und wie Sie die Tabelle in ein für Mobilgeräte optimiertes „gestapeltes“ Layout umwandeln, wenn der Platz begrenzt ist.
Empfohlene Vorgehensweise
Der Kern einer responsiven Tabelle ist die Beibehaltung der Beziehung zwischen Datenzellen und ihren Überschriften.
- Semantische Grundlage: Verwenden Sie Standard-
<table>-Elemente mit<thead>-,<tbody>- und<th>-Elementen. - Sticky Context:
position: stickywird sowohl auf Spalten- als auch auf Zeilenüberschriften angewendet. So geht der Kontext der Daten nicht verloren, egal wie weit ein Nutzer in eine Richtung scrollt. - Adaptive Transformationen: Verwenden Sie
@container-Abfragen anstelle von@media-Abfragen. So kann sich die Tabelle an ihre eigene Breite anpassen (z.B. wenn sie in einer Seitenleiste oder einem schmalen Dashboard-Widget platziert wird) und nicht an den gesamten Darstellungsbereich. - Label-Injektion: Im gestapelten Layout wird das
<thead>ausgeblendet und barrierefreie Header werden mithilfe von::before-Pseudoelementen und CSS-Variablen in jede Zelle eingefügt.
Implementierungsschritte
1. Markup und Layout
Strukturieren Sie Ihre Tabelle mit standardmäßigen semantischen Überschriften. Verwenden Sie einen .table-wrapper, um Überläufe zu verarbeiten und einen Container für Abfragen bereitzustellen. Fügen Sie dem Container ein tabindex="0"-Attribut hinzu, damit Nutzer, die nur eine Tastatur verwenden, die Tabelle einheitlich scrollen können.
Geben Sie dem Wrapper ein role="region"-Attribut, um seinen Zweck zu kommunizieren. Geben Sie ihm einen barrierefreien Namen mit aria-labelledby (unter Verweis auf das Attribut id eines vorhandenen Elements, das die Tabelle beschriftet/benennt) oder mit einem Attribut aria-label.
<div tabindex="0" role="region" aria-labelledby="employee-table-caption" class="table-wrapper">
<table>
<caption id="employee-table-caption">Current employee directory</caption>
<thead>
<tr>
<th>Employee</th>
<th>Role</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<!-- Row header remains sticky horizontally on desktop,
and becomes a sticky card header on mobile -->
<th>Alex Rivera</th>
<td>Engineer</td>
<td>Active</td>
</tr>
</tbody>
</table>
</div>
2. Fixierte Header konfigurieren
Scrollen aktivieren und Überschriften fixieren. Verwenden Sie logische Eigenschaften und explizite z-index-Werte, um die Stapelreihenfolge zu verwalten.
.table-wrapper {
overflow: auto;
max-inline-size: 100%;
max-block-size: min(500px, 80vh);
container-type: inline-size;
}
table {
border-collapse: separate;
border-spacing: 0;
}
/* Sticky column headers (desktop) */
thead {
position: sticky;
inset-block-start: 0;
z-index: 1; /* Above sticky row headers */
}
/* Sticky row headers (desktop) */
tbody th {
position: sticky;
inset-inline-start: 0;
background: white; /* Required to cover background during scroll */
}
3. Responsives gestapeltes Layout
Wenn der Platz begrenzt ist, können Sie die ursprüngliche Kopfzeile ausblenden und die Tabelle in Karten umwandeln. Labels mithilfe von Pseudoelementen einfügen. Geben Sie nur für Nichttextinhalte Alternativtext an. Andernfalls werden möglicherweise sowohl der Textinhalt als auch der barrierefreie Name von Screenreadern angesagt.
@container (width < 600px) {
table, thead, tbody, tr, th, td {
display: block;
}
thead {
/* MANDATORY: Hide column header from layout and screen readers. */
display: none
}
tr {
margin-block-end: 1.5rem;
border: 1px solid #ccc;
}
/* Sticky row headers at the top of each block/card */
tbody th {
position: sticky;
inset-block-start: 0;
z-index: 2;
background: #eee;
}
/* Define column labels as CSS variables (often set via JS) */
table {
--label-1: "Employee";
--label-2: "Role";
--label-3: "Status";
}
/* Inject labels */
td:nth-child(2)::before {
/* MANDATORY: If using non-text content, provide the accessible name (after the /) which ensures screen readers hear the text alternative. */
content: var(--label-2) ": ";
}
/* MANDATORY: Map the label for each column */
td:nth-child(3)::before {
content: var(--label-3) ": ";
}
}