Responsive Tabelle: Zeilen als Karten auf dem Smartphone

Du lieferst eine Admin-Seite mit einer Tabelle aus sieben Spalten aus, öffnest sie auf dem Smartphone und siehst den Kundennamen, eine halbe Stadt und eine horizontale Scrollleiste. Bis du beim Betrag ankommst, sind die Spaltenköpfe weggescrollt, und niemand weiß mehr, welche Zahl zu welcher Zeile gehört. Eine gute responsive Tabelle ist auf dem Smartphone meist ein anderes Layout, keine schmalere Version derselben Tabelle. Dieses Tutorial zeigt, wann Zeilen zu Karten werden, welche Spalten es auf die Karte schaffen, wie du den Wechsel auf einer normalen shadcn/ui-Tabelle ohne Hydration-Flackern baust, und wie Sortierung, Auswahl und Sammelaktionen weiter funktionieren, wenn Spaltenköpfe und Checkbox-Spalte weg sind.
Die Kurzfassung dieser Regel ist eine von neun in Mobile-first React-Komponenten. Dieser Beitrag ist die ausführliche Version.
Wann sollte eine Tabelle auf dem Smartphone zu Karten werden?
Wenn Leute einen Datensatz nach dem anderen lesen. Eine Liste von Kunden, Bestellungen, Rechnungen oder Tickets ist eine Liste von Datensätzen: Auf dem Smartphone suchst du den einen, den du brauchst, und öffnest ihn. Eine Karte pro Zeile zeigt alles Wichtige zu diesem Datensatz auf einen Blick, ohne seitliches Scrollen und ohne Spaltenkopf, den du dir merken musst.
Behalte die Tabelle, wenn es darum geht, Werte über Zeilen und Spalten hinweg zu vergleichen. Eine Zeiterfassung, eine Preismatrix, ein monatliches Umsatzraster oder ein Funktionsvergleich ergeben nur Sinn, wenn die Spalten untereinander stehen, und Karten machen genau das kaputt. Lass solche Tabellen in ihrer eigenen Box seitwärts scrollen und fixiere die erste Spalte, damit jede Zeile ihren Namen behält.
Die letzte Zeile fängt einen häufigen Fehler ab: eine „Tabelle“ mit zwei Spalten, Label und Wert, die einen einzigen Kunden beschreibt. Das ist eine Description List. Die Description List stapelt auf Smartphones das Label über dem Wert und wechselt je nach Breite ihres Containers auf eine bis drei Spalten, sie passt also in eine schmale Karte in der Seitenleiste genauso wie auf eine ganze Seite.
Warum scheitert horizontales Scrollen bei Datensätzen?
Ein 390px breiter Screen zeigt zwei oder drei Spalten einer Admin-Tabelle gleichzeitig. Um einen Datensatz zu lesen, scrollen Leute nach rechts, verlieren die Namensspalte, scrollen zurück und wiederholen das für jede Zeile. Meist ist auch der Tabellenkopf vertikal aus dem Bild gescrollt, die Zahlen, bei denen sie ankommen, haben also kein Label. Jede Zeile kostet zwei Scrollrichtungen.
Horizontales Scrollen ist außerdem das, was du standardmäßig bekommst. Ohne eigene Arbeit heißt shadcn table mobile responsive einfach Scrollen: Die Table von shadcn/ui legt das <table> in ein div mit relative w-full overflow-x-auto, eine breite Tabelle sprengt also nie das Seitenlayout, sie scrollt nur in dieser Box. Das ist der richtige Fallback und das richtige Layout für Vergleichsraster, aber bei einer Kundenliste bekommt das Smartphone damit die Desktop-Tabelle in einem Drittel der Breite.
Welche Spalten schaffen es auf eine Karte?
Fünf oder sechs, jede mit einem festen Platz. Geh deine Spalten durch und gib jeder eine Rolle:
- Titel: das Feld, an dem Leute den Datensatz erkennen. Der Kundenname, die Bestellnummer, der Betreff des Tickets. Einer pro Karte.
- Untertitel: die Zeile, die zwei ähnliche Titel unterscheidet, etwa die Stadt oder die E-Mail. Optional.
- Meta: ein Wert in der oberen Ecke, nach dem Leute die Liste absuchen. Ein Status-Badge oder ein Betrag, nicht beides.
- Felder: zwei bis vier Paare aus Label und Wert unter dem Titel. Nimm die, mit denen Leute entscheiden, ob sie den Datensatz öffnen.
- Versteckt: alles andere. Interne IDs, Erstellungsdaten, Spalten, die es nur zum Sortieren auf dem Desktop gibt.
Für eine Kundenliste mit Name, Steuernummer, Stadt, Status, Saldo, Letzte Rechnung, Betreuer und Erstellt heißt das: Name als Titel, Stadt als Untertitel, Status als Meta, Saldo, Letzte Rechnung und Betreuer als Felder, Steuernummer und Erstellt versteckt. Für die versteckten Spalten ist der Datensatz weiterhin nur einen Tap entfernt.
Aktionen wandern in ein „⋯“-Menü in der Ecke der Karte, das sich als Bottom Sheet öffnet. Inline-Icon-Buttons in einer Zeile sind mit der Maus in Ordnung; auf einer Karte konkurrieren sie mit dem Titel um ein 44px großes Touch-Ziel.
Wie machst du aus einer shadcn-Tabelle Karten auf dem Smartphone?
Render die Tabelle und eine Liste von Karten aus denselben Daten und lass CSS eine davon auswählen. Hier ist die komplette Komponente auf der Standard-Tabelle von shadcn/ui (npx shadcn@latest add table), mit einem einfachen span für den Status, damit sie nichts weiter braucht:
Das ist das ganze Muster „Tabelle als Karten“ auf dem Smartphone. Drei Details zählen mehr als das Styling.
Der Wechsel ist ein CSS-Breakpoint. hidden md:block und md:hidden stehen beide im Server-HTML, ein Smartphone zeichnet die Karten also schon beim ersten Laden. Wechselst du stattdessen mit einem useIsMobile()-Hook, rendert der Server die Tabelle (dort ist der Hook false), das Smartphone zeigt sie, und die Karten ersetzen sie erst nach der Hydration. Der Guide zum use mobile Hook erklärt, warum das passiert und wo ein Hook trotzdem das richtige Werkzeug ist.
Die Karten sind eine echte Liste. <ul> mit einem aria-label sagt einem Screenreader, wie viele Datensätze es gibt, und Paare aus <dt> und <dd> verknüpfen jeden Wert mit seinem Label. Wenn du die Tabelle mit display: none ausblendest, verschwindet sie auch aus dem Accessibility Tree, Screenreader-Nutzer auf dem Smartphone bekommen also dieselben Karten wie alle anderen und keine versteckte Tabelle.
Beide Kopien werden gerendert, das DOM hält also jede Zeile doppelt. Bei 25 Zeilen pro Seite ist das nichts. Bei Hunderten paginierst oder virtualisierst du, oder du renderst das versteckte Layout erst bei Bedarf. Ab 200 Zeilen virtualisiert die Data Table von Wingo UI das sichtbare Layout und rendert im versteckten nur einen ersten Screen mit 30 Zeilen.
Was ist mit dem reinen CSS-Trick über data-label?
Die beliebte Alternative behält ein einziges <table>, setzt unter einem Breakpoint display: block auf Zeilen und Zellen und gibt jeden Spaltenkopf über td::before { content: attr(data-label) } aus. Das spart die zweite Kopie des Markups, hat aber zwei Haken. Ein geändertes display auf Tabellenelementen hat früher deren Tabellensemantik entfernt: Adrian Roselli hält in seinem seit Jahren gepflegten Testbeitrag fest, dass Safari sie bei jedem display-Wert verloren hat, bis Safari 17 das behoben hat, und laut seinem Update vom Oktober 2023 ist display: contents der einzige Wert, der noch Probleme machen kann (Tables, CSS Display Properties, and ARIA (opens in a new tab)). Und ein Screenreader kündigt weiterhin eine Tabelle mit sieben Spalten an, während der Screen einen Stapel Karten zeigt und die Labels im CSS stecken.
Wenn du den Trick nutzt, teste mit VoiceOver auf einem iPhone und TalkBack auf Android. Wenn du das nicht testen kannst, ist die Version mit zwei Layouts oben der sicherere Standard.
Wie funktionieren Sortierung und Sammelaktionen ohne Spaltenköpfe?
Sie brauchen eigene Controls, denn die Dinge, auf die Leute am Desktop geklickt haben, sind weg. Plane jedes einzeln:
- Sortierung: eine Reihe von Chips „Sortieren nach“ über den Karten, einer pro sortierbarer Spalte, ein Tap wechselt die Richtung. Oder, bei vielen sortierbaren Spalten, ein Sortieren-Button, der ein Bottom Sheet mit Einträgen wie „Saldo, absteigend“ und „Kunde, aufsteigend“ öffnet.
- Filter: dieselbe Filter-UI wie am Desktop, nur in einem Bottom Sheet statt in einem Popover.
- Auswahl: keine Checkbox-Spalte. Ein Long Press auf eine Karte startet die Auswahl (so machen es Gmail und Fotos auf Android), danach schaltet jeder Tap eine Karte um, und solange der Auswahlmodus aktiv ist, zeigt jede Karte eine Checkbox.
- Sammelaktionen: eine Leiste, die unten am Screen schwebt, solange Zeilen ausgewählt sind, mit Padding für den Home-Indikator, statt eines Toolbar-Streifens oben, der längst weggescrollt ist.
- Paginierung: „Mehr laden“ am Ende der Liste. Nummerierte Seiten mit 32px großen Buttons sind schwer zu treffen und unterbrechen den Scrollfluss.
Ein Long Press ist unsichtbar, er darf also nicht der einzige Weg sein. Lass das „⋯“-Menü auf jeder Karte sichtbar, und wenn Auswahl wichtig ist, ergänze zusätzlich eine sichtbare Aktion „Auswählen“ in der Toolbar oder im Zeilenmenü.
Wie bekommst du dasselbe Layout mit Wingo UI?
Nimm die Table für kleine Tabellen, die nur Daten anzeigen, und die Data Table für Admin-Listen. Beide machen eine React Table mobile responsive ohne zweiten Markup-Zweig: Sie rendern die Desktop-Tabelle und das Smartphone-Layout ins Server-HTML und wechseln bei 768px per CSS, und beide verteilen die Kartenrollen aus deinen Spalten: Die erste Spalte ist der Titel, die letzte Zahlenspalte (in der Data Table hat die erste Badge-Spalte Vorrang) ist die Meta, und der Rest sind Felder, bis du mit mobile etwas anderes sagst.
Hier eine Bestelltabelle, in der die Rollen ausgeschrieben sind:
Auf dem Smartphone ist jede Bestellung eine Karte mit dem Kunden als Titel, der Stadt darunter, der Summe in der Ecke und „Bestellt am“ und „Artikel“ als Feldern. Die Bestellnummer gibt es nur am Desktop. Spalten nach dem vierten Feld wandern auf der Karte hinter einen Schalter „Mehr anzeigen“. Weil customer, placed und total sortierbar sind, erscheint über den Karten eine Reihe von Chips „Sortieren nach“, und onRowClick macht jede Karte zu einem einzigen Button, den Tastaturnutzer mit Tab erreichen.
$ npx wingo-ui@latest add tableDie Demo ist eine Rechnung mit fünf Posten und Summen. Mach dein Fenster schmaler oder öffne die Seite auf dem Smartphone, dann siehst du die Karten und die separate Summenkarte. Für eine Optik wie auf einem Kassenbon listet mobileLayout="stack" Zeilen aus Label und Wert ohne Kartenrahmen, und mobileLayout="scroll" lässt die Tabelle für Vergleichsraster seitwärts scrollen, mit fixierter erster Spalte.
Wie geht die Data Table auf dem Smartphone mit Auswahl um?
So, wie es der vorige Abschnitt beschreibt. Mit selectionMode="multiple" wählt ein Long Press von etwa 450ms eine Karte aus, danach fügen Taps Karten hinzu oder entfernen sie, und die Sammelaktionen, die du aus bulkActions zurückgibst, schweben unten. Der Sortieren-Button in der Toolbar öffnet ein Bottom Sheet mit Optionen im Stil von „Saldo, absteigend“, Filter und Zeilenmenü öffnen sich ebenfalls als Bottom Sheets, und mobilePagination="load-more" (der Standard) hängt Zeilen an.
Den Rest erledigen hier die Standards: Kunde wird zum Titel, weil die Spalte als erste kommt, und Status wird zur Meta, weil es die erste Badge-Spalte ist. Saldo und Betreuer sind die Felder. Die Data Table zeigt höchstens vier Felder und lässt die übrigen auf der Karte weg, stell die wichtigen also nach vorn oder markiere den Rest bewusst als hidden.
$ npx wingo-ui@latest add data-tableReicht die Zuordnung der Spalten nicht, nehmen beide Komponenten eine renderCard-Funktion für eine eigene Karte, und mobileLayout="scroll" nimmt eine einzelne Tabelle von den Karten aus.
Was, wenn die Zeilen einfacher sind als eine Tabelle?
Dann lass die Tabelle auf jeder Bildschirmgröße weg. Eine Liste von Teammitgliedern, Benachrichtigungen oder Einstellungen mit einem Namen, einer Detailzeile und einem Wert ist eine Liste, und die List gibt ihr Zeilen mit Avatar oder Icon, einem Wert am Ende, einem Zeilenmenü und Swipe-Aktionen auf Touch-Geräten (nach links wischen zum Archivieren oder Löschen, mit einem Sheet per Long Press als sichtbarer Alternative). Sie liest sich bei 390px und bei 1440px gut, ganz ohne Breakpoint.
Wie testest du eine responsive Tabelle auf dem Smartphone?
Prüf das, was am häufigsten kaputtgeht: Die Seite darf in Smartphone-Breite nie seitwärts scrollen. Ein Playwright-Test fängt das in der CI ab:
Ergänze einen Fall mit 360px für kleine Android-Smartphones und lass ihn in Light und Dark laufen. Mach danach einen Durchgang auf einem echten Smartphone: Long Press auf eine Karte, Zeilenmenü und Sortier-Control öffnen, und prüfen, dass die Leiste für Sammelaktionen über dem Home-Indikator sitzt.
Wo fängst du am besten an?
Nimm deine meistgenutzte Admin-Tabelle, gib jeder Spalte auf Papier eine Rolle und bau das Kartenlayout mit md:hidden neben die Tabelle. Willst du lieber mit Komponenten starten, die Zuordnung, Sortier-Chips, Auswahl per Long Press und die schwebende Leiste für Sammelaktionen schon mitbringen: Table und Data Table gehören zu Wingo UI Pro; füg eine mit npx wingo-ui@latest add data-table hinzu. Den Aufbau von Filtern und Server-Pagination auf TanStack Schritt für Schritt zeigt das Tutorial zur TanStack Table v9 Data Table (auf Englisch), und weitere Smartphone-Layouts findest du in Mobile UI in React.
Häufige Fragen
Wie mache ich eine Tabelle auf dem Smartphone responsive?
Kläre zuerst, ob Leute Datensätze lesen oder Spalten vergleichen. Für Datensätze renderst du unter 768px eine Karte pro Zeile mit dem wichtigsten Feld als Titel und zwei bis vier beschrifteten Feldern; Vergleichsraster bleiben Tabellen, die in ihrer Box seitwärts scrollen, mit fixierter erster Spalte.
Ist die shadcn Table auf dem Smartphone responsive?
Sie scrollt. Die Table von shadcn/ui legt die Tabelle in ein div mit overflow-x-auto, eine breite Tabelle scrollt also seitwärts in ihrem Container. Zeilen in Karten zu verwandeln liegt bei dir, meist mit einem zweiten Markup-Zweig, den md:-Klassen ein- und ausblenden.
Sollte ich display: block auf Tabellenzeilen setzen, um Karten zu bekommen?
Optisch klappt das, und du hast nur eine Kopie des Markups, aber bis Safari 17 hat ein geändertes display auf Tabellenelementen in Safari die Tabellensemantik entfernt (Adrian Roselli, Oktober 2023), und display: contents kann weiterhin Probleme machen. Eine eigene Liste von Karten mit echten dt- und dd-Labels bekommst du für Screenreader leichter richtig hin.
Wie sortieren Nutzer eine Tabelle auf dem Smartphone ohne Spaltenköpfe?
Gib ihnen ein Control, das die Aufgabe der Spaltenköpfe übernimmt: eine Reihe von Chips „Sortieren nach“ über den Karten oder einen Sortieren-Button, der ein Bottom Sheet mit jeder Spalte auf- und absteigend öffnet.
Sollte ich mit useIsMobile zwischen Tabelle und Karten wechseln?
Nein. Ein Media-Query-Hook ist auf dem Server false, also rendern Smartphones zuerst die Tabelle und wechseln erst nach der Hydration zu den Karten. Render beide und blende eine mit md:hidden und hidden md:block aus, das stimmt schon beim ersten Paint.
- Tabellen
- Mobile
- Responsive Design
- React
- Tailwind CSS


