Table
Semantische Datentabelle mit optionalem Textfilter und clientseitiger Spalten-Sortierung. Responsive via Container Query: unter 40rem Container-Breite stacken Zeilen zu Karten (Column-Header bleibt für Screenreader erhalten, wird visuell als Label pro Zelle eingeblendet).
Basis
Minimales Beispiel: Spalten + Zeilen. Erste Spalte wird standardmäßig als Row-Header (<th scope="row">) gerendert.
Einfache Tabelle
| Produkt | Preis | Lager |
|---|---|---|
| Standard-Abo | 19,90 | 128 |
| Komfort-Abo | 29,90 | 42 |
| Premium-Abo | 49,90 | 12 |
| Einzelticket | 3,20 | 999 |
Keine Treffer.
<Table
columns={[
{ key: 'produkt', label: 'Produkt', isRowHeader: true },
{ key: 'preis', label: 'Preis', align: 'right' },
{ key: 'lager', label: 'Lager', align: 'right' },
]}
rows={[
{ produkt: 'Standard-Abo', preis: '19,90', lager: 128 },
{ produkt: 'Komfort-Abo', preis: '29,90', lager: 42 },
{ produkt: 'Premium-Abo', preis: '49,90', lager: 12 },
]}
/> Sortierbar
Spalten mit sortable: true erzeugen data-sortable="true" auf dem <th>. Klick auf den Header-Button toggelt aria-sort zwischen ascending und descending. Clientseitige Sortierung erkennt deutsche Zahlenformate (1.234,56).
Sortierbare Spalten
| Produkt | ||
|---|---|---|
| Standard-Abo | 19,90 | 128 |
| Komfort-Abo | 29,90 | 42 |
| Premium-Abo | 49,90 | 12 |
| Einzelticket | 3,20 | 999 |
Keine Treffer.
<Table
columns={[
{ key: 'produkt', label: 'Produkt', isRowHeader: true },
{ key: 'preis', label: 'Preis', sortable: true, align: 'right' },
{ key: 'lager', label: 'Lager', sortable: true, align: 'right' },
]}
rows={produkteRows}
/> Mit Filter
Die filterable-Prop blendet ein Suchfeld oberhalb der Tabelle ein. Der Filter matcht case-insensitive über alle sichtbaren Zellen. Bei leeren Treffern wird ein Platzhaltertext angezeigt.
Filterbare Tabelle
| Fläche (km2) | 4.943 | 8.221 | 4.115 | 34.112 |
|---|---|---|---|---|
| Einwohner | 5,1 Mio. | 5,9 Mio. | 3,2 Mio. | 18,1 Mio. |
| Einwohner/km2 | 1.032 | 718 | 778 | 531 |
| Bahnstationen | 170 | 282 | 198 | 715 |
| Schienennetz (km) | 820 | 1.420 | 935 | 3.502 |
| Elektrifizierungsquote | 82 % | 76 % | 88 % | 81 % |
| Zugkilometer (Mio.) | 32,4 | 41,8 | 28,9 | 115,4 |
Keine Treffer.
<Table
caption="Strukturdaten 2025 NRW"
filterable
columns={strukturColumns}
rows={strukturRows}
/> Responsive (Container Query)
Die Tabelle beobachtet die Breite ihres Containers (nicht des Viewports).
Unterhalb von 40rem wird jede Zeile zur abgerundeten Karte
mit dem Row-Header als Titel. Jede Zelle zeigt ihr Spaltenlabel via data-label
(kommt automatisch aus der Column-Config).
Ziehe das Browserfenster schmal, um das Stacked-Layout zu sehen. Oder platziere die Tabelle in einer schmalen Spalte (Sidebar-Grid):
Tabelle in schmaler Spalte
| Produkt | ||
|---|---|---|
| Standard-Abo | 19,90 | 128 |
| Komfort-Abo | 29,90 | 42 |
| Premium-Abo | 49,90 | 12 |
| Einzelticket | 3,20 | 999 |
Keine Treffer.
<div class="max-w-sm">
<Table columns={produkteColumns} rows={produkteRows} />
</div> Props
| Prop | Typ | Beschreibung |
|---|---|---|
| caption | string? | Visuell versteckte <caption> für Screenreader. |
| columns | Column[] | Spalten-Definition: key, label, optional sortable, align, isRowHeader. |
| rows | Record[] | Array von Objekten, Keys entsprechen den Column-Keys. |
| filterable | boolean | Zeigt Filter-Input. Default false. |
| filterPlaceholder | string? | Placeholder-Text des Filter-Inputs. |
| class | string? | Zusätzliche Klassen auf dem Wrapper. |
Column-Konfiguration
| Feld | Typ | Beschreibung |
|---|---|---|
| key | string | Feldname in den Row-Objekten. |
| label | string | Header-Text und Stacked-Mode-Label. |
| sortable | boolean? | Macht Spalte clientseitig sortierbar. |
| align | 'left' \| 'right' \| 'center' | Text-Alignment für <th> und <td>. |
| isRowHeader | boolean? | Rendert Zelle als <th scope="row">. Default: erste Spalte. |
Barrierefreiheit
- Korrekte Tabellensemantik mit
<thead>,<tbody>,scope="col"undscope="row". - Sortier-Buttons mit
aria-sort(none/ascending/descending). - Filter-Input mit
<label>(sr-only) undtype="search". - Im Stacked-Layout bleibt
<thead>erhalten (visually hidden via clip), Screenreader lesen die Spaltenueberschriften mit. - Caption via
caption-Prop für Screenreader-Kontext.
Installation
cp-components add table