{
 "$schema": "../../schema.json",
 "schema": "fundus/1",
 "id": "tabelle-schmal-robust",
 "name": "Tabelle schmal und robust",
 "name_en": "Wide data tables on narrow screens",
 "kurz": "Richtpreis-Tabelle eines Umzugsbetriebs bleibt auf jeder Breite eine echte Tabelle: feste erste Spalte mit scroll-getriebenen Schatten, Zeilen als Karten per Container-Abfrage oder Spalten nach Vorrang mit Menü.",
 "kategorie": "Zugang & Robustheit",
 "kategorie_slug": "zugang",
 "kategorie_nr": 17,
 "bau_chat": "E",
 "prioritaet": "E",
 "trendstatus": "etabliert",
 "belegstatus": "quellenbelegt",
 "website_belege": 0,
 "stimmung": [
  "ruhig",
  "präzise"
 ],
 "stimmung_roh": [
  "präzise",
  "ruhig"
 ],
 "technik": [
  "css",
  "vanilla-js",
  "position-sticky",
  "scroll-driven-css",
  "container-queries",
  "intl"
 ],
 "technik_gruppen": [
  "css",
  "js"
 ],
 "leistungskosten": "niedrig",
 "steckbrief": {
  "beschreibung": "Breite Daten- und Preistabellen bleiben auf dem Telefon echte <table> mit <caption>, scope und Tastaturbedienung. Drei Wege je nach Inhalt: erste Spalte fest und waagerecht scrollen mit Schatten, die zeigen, dass es weitergeht (scroll-getrieben, inzwischen in allen Engines). Oder unter einer Containerbreite jede Zeile als Karte, die Spaltenköpfe stehen als Etiketten davor. Oder Spalten ein- und ausblenden mit Vorrang, was auf dem Telefon zuerst bleibt.",
  "warum_es_wirkt": "Tabellen sind auf dem Telefon die häufigste Stelle für Querscrollen der ganzen Seite (WCAG 1.4.10). Eine Tabelle, die in ihrem Rahmen scrollt und das sichtbar macht, hält die Seite ruhig. Karten lesen sich bei wenigen Spalten besser als Wischen. Die Semantik bleibt dabei erhalten, damit Screenreader Zeile und Spalte ansagen.",
  "wann_einsetzen": "Richtpreise nach Wohnungsgröße oder Leistung, technische Daten mit vielen Maßen, Fahr- und Kurspläne, Gebührenübersichten, Liefergebiete. Umzug, Industrie, Kurs, Fitness, IT, Handwerk.",
  "wann_nicht": "Nicht für Paketvergleiche mit Häkchen (dafür vergleichstabelle-sticky) und nicht für Wochenpläne (wochen-kursplan). Keine Tabelle für Layout. Karten nur bis etwa sechs Spalten; danach verliert man den Vergleich zwischen Zeilen.",
  "varianten": [
   "Feste Spalte: erste Spalte sticky, Rahmen scrollt waagerecht, Schatten per animation-timeline: scroll(nearest inline), Rückfall per scroll-Ereignis",
   "Karten: unter 560 px Containerbreite wird jede Zeile eine Karte, Spaltenköpfe als Etiketten per attr(data-kopf), Rollen nachgetragen",
   "Spalten wählen: Vorrang je Spalte (1 immer, 2 ab 520 px, 3 ab 900 px Containerbreite), Menü „Spalten n von 8“ zum Zu- und Abschalten"
  ],
  "umsetzung": "1. Immer <table> mit <caption id>, <thead> mit <th scope=col>, erste Zelle je Zeile <th scope=row>. Zahlen rechtsbündig mit tabellarischen Ziffern, Einheiten im Kopf. 2. Rahmen: <div class=rahmen tabindex=0 role=region aria-labelledby=caption-id> mit overflow-x: auto, sichtbarem Fokusrahmen und overscroll-behavior-x: contain. 3. Feste Spalte: th[scope=row] { position: sticky; left: 0 } mit eigenem Hintergrund. Schatten: zwei Pseudo-Elemente am Rahmen, sticky an den Kanten, Deckkraft per animation: schatten linear both; animation-timeline: scroll(self inline) (Zeile nach der Kurzschreibweise). Rückfall ohne Unterstützung über @supports not (animation-timeline: scroll()): Schatten per JS-Klasse am scroll-Ereignis. tabindex nur, solange der Inhalt breiter ist (ResizeObserver). 4. Karten: container-type: inline-size am Rahmen; @container (max-width: 560px) setzt tr auf display: grid und td::before { content: attr(data-kopf) }. Weil display auf Tabellenteilen in manchen Browsern die Rollen löscht, die Rollen per role=table/row/cell/rowheader/columnheader im Kartenmodus nachtragen (JS beim Umschalten) und den thead visuell verstecken, nicht entfernen. 5. Spalten wählen: data-vorrang=1…3 an den Spalten; unter 640 px nur Vorrang 1 sichtbar; ein Menü mit Checkboxen schaltet Spalten über eine Klasse am table zu (col-Ausblendung per :nth-child), Ansage „5 von 8 Spalten sichtbar“. 6. Keine Tabelle in einem zweiten senkrechten Scrollbereich.",
  "mobil": "Das ist der Kern. Feste Spalte: Wischen im Rahmen, Seite bleibt stehen, rechter Schatten zeigt „weiter“. Karten: keine Querbewegung, jede Zeile untereinander. Spalten wählen: drei Spalten passen, Menü als große Checkboxen.",
  "reduzierte_bewegung": "Schatten bleiben (keine Bewegung, nur Zustand). Spalten erscheinen ohne Überblenden, das Menü öffnet ohne Gleiten.",
  "barrierefreiheit": "Echte Tabellensemantik in allen Modi, auch als Karten (Rollen nachgetragen). Rahmen fokussierbar mit Namen aus der caption, damit Tastaturnutzer ihn mit Pfeiltasten scrollen können. Spaltenköpfe in Karten als sichtbare Etiketten. Ausgeblendete Spalten per display: none, nicht nur verdeckt. Kein waagerechtes Scrollen der Seite bei 320 px. Preise mit Hinweis „Richtwert, Angebot nach Besichtigung“ (vom Kunden prüfen lassen)."
 },
 "messwerte": {
  "easing": "cubic-bezier(0.16, 1, 0.3, 1) ein, cubic-bezier(0.7, 0, 0.84, 0) aus (Rezept umzug-transport-logistik, UI-Paar)",
  "dauer_ms": "Schatten scroll-getrieben (keine Zeit), Spalte ein 300, aus 200, Menü 300/200",
  "stagger_ms": "",
  "distanz": "Schatten 24 px breit; Karten ab Containerbreite unter 560 px",
  "sonstiges": "Erste Spalte 9 bis 12 rem, Tabelle min-width 760 px",
  "herkunft": "eigene Festlegung",
  "quelle": "Rezept umzug-transport-logistik"
 },
 "kurve": {
  "bezier": [
   0.16,
   1.0,
   0.3,
   1.0
  ],
  "css": "cubic-bezier(0.16, 1, 0.3, 1)",
  "name": "expo.out",
  "dauer_ms": 300
 },
 "gesehen_bei": [
  {
   "seite": "Adrian Roselli: Under-Engineered Responsive Tables",
   "url": "https://adrianroselli.com/2020/11/under-engineered-responsive-tables.html",
   "auszeichnung": "",
   "beobachtung": "Abgerufen 05.11.2026 (17.11.2020, aktualisiert 05.10.2026): Rahmen mit role=region, aria-labelledby auf die caption und tabindex=0, overflow: auto nur bei vollständigem Markup; keine display-Werte, die die Tabellensemantik brechen; Warnung vor verschachtelten Scrollbereichen (09/2025). Nachtrag 10/2026: fokussierbare Elemente brauchen nicht unbedingt eine Rolle.",
   "art": "artikel"
  },
  {
   "seite": "Ben Frain: Scroll indicators on tables with background colours with animation-timeline",
   "url": "https://benfrain.com/scroll-indicators-on-tables-with-background-colours-with-animation-timeline/",
   "auszeichnung": "",
   "beobachtung": "Abgerufen 05.11.2026 (13.02.2026): Schatten als sticky Pseudo-Elemente mit Verlauf, Deckkraft per animation-timeline: scroll(nearest inline), linker Schatten rückwärts. animation-timeline muss nach der animation-Kurzschreibweise stehen.",
   "art": "artikel"
  },
  {
   "seite": "MDN: Firefox 159 Release Notes",
   "url": "https://developer.mozilla.org/Firefox/Releases/159",
   "auszeichnung": "",
   "beobachtung": "Laut Recherche Runde 4 (geöffnet 04.11.2026): scroll-getriebene Animationen in Firefox 159 (27.10.2026) standardmäßig an, damit in allen Engines.",
   "art": "artikel"
  }
 ],
 "tutorials": [
  {
   "titel": "Adrian Roselli: Under-Engineered Responsive Tables",
   "url": "https://adrianroselli.com/2020/11/under-engineered-responsive-tables.html"
  },
  {
   "titel": "W3C WAI: Tables Tutorial",
   "url": "https://www.w3.org/WAI/tutorials/tables/"
  },
  {
   "titel": "MDN: animation-timeline scroll()",
   "url": "https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timeline/scroll"
  }
 ],
 "kombinierbar_mit": [
  "tabellenziffern",
  "vergleichstabelle-sticky",
  "technische-daten-produktseite",
  "reparatur-richtpreise",
  "zoom-und-textabstand-robust",
  "fokus-stil-system",
  "kontrastmodus-forced-colors"
 ],
 "urls": {
  "detail": "https://cecodesign.de/lab/fundus/zugang/tabelle-schmal-robust/",
  "meta": "https://cecodesign.de/lab/fundus/zugang/tabelle-schmal-robust/meta.json",
  "demo": "https://cecodesign.de/lab/fundus/zugang/tabelle-schmal-robust/demo"
 },
 "herkunft": {
  "quelldateien": [
   "Recherche Runde 4, 04.11.2026"
  ],
  "pruefnotizen": [
   "Recherche Runde 4: vergleichstabelle-sticky, technische-daten-produktseite und wochen-kursplan lösen schmale Tabellen je einzeln; ein allgemeines Muster für Datentabellen fehlte.",
   "Abgrenzung: vergleichstabelle-sticky ist der Paketvergleich (Merkmale × Pakete, Paket-Umschalter). Dieses Muster ist für Datentabellen mit vielen Zeilen und Spalten und zeigt drei Strategien zur Wahl.",
   "Richtpreise in der Demo sind erfundene Beispielwerte; echte Preise und Pflichtangaben (PAngV) vom Kunden prüfen lassen.",
   "Demo 05.11.2026: font-variant-numeric: tabular-nums auf der ganzen Tabelle macht in Schibsted Grotesk auch Komma und Punkt im Fließtext breit; nur an Zahlenzellen setzen."
  ]
 },
 "bau": {
  "status": "fertig",
  "kurz": "Richtpreis-Tabelle eines Umzugsbetriebs bleibt auf jeder Breite eine echte Tabelle: feste erste Spalte mit scroll-getriebenen Schatten, Zeilen als Karten per Container-Abfrage oder Spalten nach Vorrang mit Menü.",
  "demo": true,
  "abhaengigkeiten": [],
  "werte": {
   "semantik": "table > caption#cap, thead th scope=col, tbody th scope=row; Rahmen div role=region aria-labelledby=cap, tabindex=0 nur wenn scrollWidth > clientWidth (ResizeObserver)",
   "feste_spalte": "th[scope=row] und erste Kopfzelle position: sticky; left: 0; Breite 10,5 rem (≤ 760 px: 8,5 rem, ≤ 360 px: 7,5 rem); Tabelle min-width 820 px",
   "schatten": "::before/::after des Rahmens, sticky (links an --spalte1, rechts an 0), 24 px Verlauf rgba(24,25,27,.2); animation linear both; animation-timeline: scroll(nearest inline); links 0 → 1 bis 6 %, rechts 1 → 0 ab 94 %",
   "schatten_rueckfall": "@supports not (animation-timeline: scroll()): data-links / data-rechts per scroll-Ereignis",
   "karten": "@container tabelle (max-width: 560px): tr grid 2 Spalten, th als Kartenkopf auf Planengrau, td display: contents mit ::before attr(data-kopf); thead visuell versteckt; Rollen table/rowgroup/row/columnheader/rowheader/cell per JS nachgetragen",
   "spalten": "data-vorrang: 1 immer (Wohnung, Fläche, Richtpreis), 2 ab 520 px (Umzugsgut, Dauer), 3 ab 900 px (Team, Fahrzeug, Halteverbot) Containerbreite; Menü details mit Checkboxen; zugeschaltete Spalte 300 ms Hinterlegung #FFF3C9",
   "menue": "öffnen 300 ms cubic-bezier(0.16, 1, 0.3, 1) 6 px, Pfeil dreht zu 200 ms cubic-bezier(0.7, 0, 0.84, 0); Esc schließt, Klick daneben schließt",
   "ansage": "aria-live „Team eingeblendet. 6 von 8 Spalten sichtbar.“",
   "zahlen": "tabular-nums nur in Zahlenzellen (sonst werden Komma und Punkt breit), rechtsbündig, Intl.NumberFormat de-DE",
   "breite_testen": "Feld max-width 640 / 360 px in 300 ms cubic-bezier(0.16, 1, 0.3, 1), zeigt die Container-Abfragen am Desktop",
   "schrift": "Special Gothic wdth 75 wght 700 h1 clamp(2.5rem, 1.4rem + 4.6vw, 5.4rem), Schibsted Grotesk 17 px, Köpfe 12,5 px Versal +0.06em",
   "farben": "#F2F2EF Grund, #18191B Text, #DFE0DC Planengrau, #24272A Asphalt, #F2B705 Flottengelb nur mit 1,5–2 px Kontur"
  },
  "ausloeser": "scroll",
  "touch": "Feste Spalte: Wischen im Rahmen, Seite bleibt stehen, rechter Schatten und Hinweis „seitlich wischen“. Karten: keine Querbewegung. Spalten: drei Spalten auf dem Telefon, Menü mit 44-px-Zeilen. Der Breiten-Simulator ist auf dem Telefon aus (echte Breite).",
  "reduziert": "Schatten bleiben (Zustand, keine Bewegung), Breitenwechsel, Menü und Spaltenhinterlegung ohne Übergang, Vorführung scrollt ohne Gleiten.",
  "korrekturen": {
   "varianten": [
    "Feste Spalte: erste Spalte sticky, Rahmen scrollt waagerecht, Schatten per animation-timeline: scroll(nearest inline), Rückfall per scroll-Ereignis",
    "Karten: unter 560 px Containerbreite wird jede Zeile eine Karte, Spaltenköpfe als Etiketten per attr(data-kopf), Rollen nachgetragen",
    "Spalten wählen: Vorrang je Spalte (1 immer, 2 ab 520 px, 3 ab 900 px Containerbreite), Menü „Spalten n von 8“ zum Zu- und Abschalten"
   ]
  },
  "geprueft": "05.11.2026",
  "notiz": "Welt: Rezept umzug-transport-logistik, erfundene Krahnert Umzüge in Göppingen. Special Gothic und Schibsted Grotesk, Asphalt, Planengrau, Flottengelb nur am Preis mit Kontur. Sechs Wohnungsgrößen, acht Spalten, Preise sind erfundene Beispielwerte (PAngV und Endpreis vom Kunden prüfen lassen). Quellen: Adrian Roselli (Rahmen mit Rolle, Name, tabindex; Stand 05.10.2026), Ben Frain (Schatten per animation-timeline, 13.02.2026). Vorführung: feste Spalte bei 640 px hin und zurück scrollen, Karten von breit auf 360 px, Spalten: Menü öffnen, Team zu- und abschalten. Abgrenzung: vergleichstabelle-sticky (Paketvergleich). Selbsturteil 4.",
  "varianten": [
   {
    "id": "feste",
    "name": "Feste erste Spalte",
    "wann": "Viele Spalten, die man vergleicht (Richtpreise, technische Daten, Fahrpläne): Zeilenkopf bleibt stehen, der Rest scrollt.",
    "werte": {
     "modus": "sticky th + Schatten scroll-getrieben",
     "min_breite": "820 px"
    }
   },
   {
    "id": "karten",
    "name": "Zeilen als Karten",
    "wann": "Bis etwa sechs Spalten, bei denen jede Zeile für sich gelesen wird (Leistungen, Gebühren, Liefergebiete).",
    "werte": {
     "modus": "@container (max-width: 560px), td::before attr(data-kopf)",
     "rollen": "per JS nachgetragen"
    }
   },
   {
    "id": "spalten",
    "name": "Spalten nach Vorrang",
    "wann": "Breite Datentabellen, bei denen auf dem Telefon zwei, drei Spalten reichen und der Rest auf Wunsch kommt.",
    "werte": {
     "modus": "data-vorrang 1/2/3, Grenzen 520 und 900 px Containerbreite",
     "menue": "details mit Checkboxen, Ansage n von 8"
    }
   }
  ]
 }
}
