Tabelle

Übergib columns und rows, und die Tabelle baut sich selbst, Kopfzeile inklusive. Sie steckt in einem horizontalen Scroll-Container, sodass eine Tabelle mit mehr Spalten, als ein Telefon zeigen kann, für sich scrollt, statt die Seite zu dehnen. Exportiert als table und als dataTable.

Einfache Tabelle

SeiteGrößeRenderzeit
/4,1 kB12 ms
/docs12,7 kB31 ms
/examples9,4 kB24 ms
table({
  columns: [
    { key: 'page', header: 'Seite' },
    { key: 'size', header: 'Größe' },
    { key: 'time', header: 'Renderzeit' },
  ],
  rows: [
    { page: '/', size: '4,1 kB', time: '12 ms' },
    { page: '/docs', size: '12,7 kB', time: '31 ms' },
    { page: '/examples', size: '9,4 kB', time: '24 ms' },
  ],
})

Ausrichtung

Zahlen lesen sich besser, wenn sie am Ende ihrer Spalte ausgerichtet sind.

SeiteBytesGzip
/4.1121.204
/docs12.7043.910
/examples9.3882.744
table({
  columns: [
    { key: 'page', header: 'Seite' },
    { key: 'bytes', header: 'Bytes', align: 'end' },
    { key: 'gzip', header: 'Gzip', align: 'end' },
  ],
  rows: [
    { page: '/', bytes: '4.112', gzip: '1.204' },
    { page: '/docs', bytes: '12.704', gzip: '3.910' },
    { page: '/examples', bytes: '9.388', gzip: '2.744' },
  ],
})

Eigene Zellen

Eine Spalte mit einer render-Funktion bekommt die ganze Zeile und gibt zurück, was in der Zelle stehen soll — einen Chip, einen Link, eine formatierte Zahl.

SeiteGrößeStatus
/docs/routing18,2 kBok
/docs/data21,7 kBok
/docs/islands24,1 kBfehlgeschlagen
table({
  columns: [
    { header: 'Seite', render: (row) => link({ href: row.href }, row.page) },
    { key: 'size', header: 'Größe', align: 'end' },
    { header: 'Status', align: 'end', render: (row) =>
      chip({ size: 'sm', dot: true, color: row.ok ? 'success' : 'danger' }, row.ok ? 'ok' : 'fehlgeschlagen') },
  ],
  rows: [
    { page: '/docs/routing', href: '/de/docs/routing', size: '18,2 kB', ok: true },
    { page: '/docs/data', href: '/de/docs/data', size: '21,7 kB', ok: true },
    { page: '/docs/islands', href: '/de/docs/islands', size: '24,1 kB', ok: false },
  ],
})

Stile

striped unterlegt jede zweite Zeile, hover hebt die Zeile unter dem Zeiger hervor, und dense zieht den Innenabstand für eine Tabelle mit vielen Zeilen enger.

gestreift
NameWert
Seiten169
Assets208
gesamt9,5 MB
Hover und dicht
NameWert
Seiten169
Assets208
gesamt9,5 MB
stack({ gap: 'lg' },
  table({
    striped: true,
    caption: 'gestreift',
    columns: [{ key: 'name', header: 'Name' }, { key: 'value', header: 'Wert', align: 'end' }],
    rows: [{ name: 'Seiten', value: '169' }, { name: 'Assets', value: '208' }, { name: 'gesamt', value: '9,5 MB' }],
  }),
  table({
    hover: true,
    dense: true,
    caption: 'Hover und dicht',
    columns: [{ key: 'name', header: 'Name' }, { key: 'value', header: 'Wert', align: 'end' }],
    rows: [{ name: 'Seiten', value: '169' }, { name: 'Assets', value: '208' }, { name: 'gesamt', value: '9,5 MB' }],
  }),
)

Beschriftung

Eine Beschriftung benennt die Tabelle für alle, die ohne den umgebenden Text bei ihr landen — lohnt sich immer dann, wenn die Tabelle nicht direkt unter einer Überschrift steht, die schon sagt, was sie ist.

Build-Ausgabe, neueste zuerst
CommitWannSeiten
94a837avor 4 Minuten169
dcfaaaevor 2 Stunden161
table({
  caption: 'Build-Ausgabe, neueste zuerst',
  columns: [
    { key: 'commit', header: 'Commit' },
    { key: 'when', header: 'Wann' },
    { key: 'pages', header: 'Seiten', align: 'end' },
  ],
  rows: [
    { commit: '94a837a', when: 'vor 4 Minuten', pages: '169' },
    { commit: 'dcfaaae', when: 'vor 2 Stunden', pages: '161' },
  ],
})

Aus Daten

Zeilen sind ein gewöhnliches Array, also meist genau das, was data() ohnehin geladen hat — ohne Adapter dazwischen.

BeitragVeröffentlichtAufrufe
Hallo Welt2026-01-141.204
Statisch zuerst2026-02-02890
Kein Runtime2026-03-192.317
return (() => {
  const posts = [
    { title: 'Hallo Welt', date: '2026-01-14', reads: 1204 },
    { title: 'Statisch zuerst', date: '2026-02-02', reads: 890 },
    { title: 'Kein Runtime', date: '2026-03-19', reads: 2317 },
  ]

  return table({
    hover: true,
    columns: [
      { key: 'title', header: 'Beitrag' },
      { key: 'date', header: 'Veröffentlicht' },
      { header: 'Aufrufe', align: 'end', render: (post) => post.reads.toLocaleString('de') },
    ],
    rows: posts,
  })
})()

Das Markup selbst schreiben

Lass columns weg, und die Tabelle rendert stattdessen ihre Kinder — eine Tabelle mit Fußzeile oder gruppierten Kopfzeilen lässt sich also von Hand bauen und bekommt trotzdem Gestaltung und Scroll-Container.

Props

PropTypStandardBeschreibung
columnsTableColumn[]—{ key, header, align, render } je Spalte. Weglassen, um die Zeilen von Hand zu schreiben.
rowsobject[][]Ein Objekt pro Zeile.
captionChild—Eine Beschriftung über der Tabelle.
stripedbooleanfalseJede zweite Zeile unterlegen.
hoverbooleanfalseDie Zeile unter dem Zeiger hervorheben.
densebooleanfalseEngerer Zellabstand.