Tabla

Pasa columns y rows y la tabla se construye sola, cabecera incluida. Va envuelta en un contenedor con scroll horizontal, así que una tabla con más columnas de las que cabe en un móvil se desplaza por su cuenta en vez de estirar la página. Se exporta como table y como dataTable.

Tabla básica

PáginaTamañoTiempo de render
/4,1 kB12 ms
/docs12,7 kB31 ms
/examples9,4 kB24 ms
table({
  columns: [
    { key: 'page', header: 'Página' },
    { key: 'size', header: 'Tamaño' },
    { key: 'time', header: 'Tiempo de render' },
  ],
  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' },
  ],
})

Alineación

Los números se leen mejor alineados al final de su columna.

PáginaBytesGzip
/4.1121.204
/docs12.7043.910
/examples9.3882.744
table({
  columns: [
    { key: 'page', header: 'Página' },
    { 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' },
  ],
})

Celdas a medida

Una columna con función render recibe la fila entera y devuelve lo que deba ir en la celda: un chip, un enlace, un número formateado.

PáginaTamañoEstado
/docs/routing18,2 kBok
/docs/data21,7 kBok
/docs/islands24,1 kBfallo
table({
  columns: [
    { header: 'Página', render: (row) => link({ href: row.href }, row.page) },
    { key: 'size', header: 'Tamaño', align: 'end' },
    { header: 'Estado', align: 'end', render: (row) =>
      chip({ size: 'sm', dot: true, color: row.ok ? 'success' : 'danger' }, row.ok ? 'ok' : 'fallo') },
  ],
  rows: [
    { page: '/docs/routing', href: '/es/docs/routing', size: '18,2 kB', ok: true },
    { page: '/docs/data', href: '/es/docs/data', size: '21,7 kB', ok: true },
    { page: '/docs/islands', href: '/es/docs/islands', size: '24,1 kB', ok: false },
  ],
})

Estilos

striped alterna bandas en las filas, hover resalta la fila bajo el puntero, y dense aprieta el relleno para una tabla con muchas filas.

con bandas
NombreValor
páginas169
recursos208
total9,5 MB
con hover y densa
NombreValor
páginas169
recursos208
total9,5 MB
stack({ gap: 'lg' },
  table({
    striped: true,
    caption: 'con bandas',
    columns: [{ key: 'name', header: 'Nombre' }, { key: 'value', header: 'Valor', align: 'end' }],
    rows: [{ name: 'páginas', value: '169' }, { name: 'recursos', value: '208' }, { name: 'total', value: '9,5 MB' }],
  }),
  table({
    hover: true,
    dense: true,
    caption: 'con hover y densa',
    columns: [{ key: 'name', header: 'Nombre' }, { key: 'value', header: 'Valor', align: 'end' }],
    rows: [{ name: 'páginas', value: '169' }, { name: 'recursos', value: '208' }, { name: 'total', value: '9,5 MB' }],
  }),
)

Título de tabla

Un caption nombra la tabla para quien llegue a ella sin el texto de alrededor: merece la pena siempre que la tabla no esté justo debajo de un encabezado que ya diga qué es.

Salida de la compilación, la más reciente primero
CommitCuándoPáginas
94a837ahace 4 minutos169
dcfaaaehace 2 horas161
table({
  caption: 'Salida de la compilación, la más reciente primero',
  columns: [
    { key: 'commit', header: 'Commit' },
    { key: 'when', header: 'Cuándo' },
    { key: 'pages', header: 'Páginas', align: 'end' },
  ],
  rows: [
    { commit: '94a837a', when: 'hace 4 minutos', pages: '169' },
    { commit: 'dcfaaae', when: 'hace 2 horas', pages: '161' },
  ],
})

Desde datos

Las filas son un array normal, así que suelen ser lo que data() ya cargó, sin adaptador de por medio.

EntradaPublicadaLecturas
Hola mundo2026-01-141204
Primero lo estático2026-02-02890
Sin runtime2026-03-192317
return (() => {
  const posts = [
    { title: 'Hola mundo', date: '2026-01-14', reads: 1204 },
    { title: 'Primero lo estático', date: '2026-02-02', reads: 890 },
    { title: 'Sin runtime', date: '2026-03-19', reads: 2317 },
  ]

  return table({
    hover: true,
    columns: [
      { key: 'title', header: 'Entrada' },
      { key: 'date', header: 'Publicada' },
      { header: 'Lecturas', align: 'end', render: (post) => post.reads.toLocaleString('es') },
    ],
    rows: posts,
  })
})()

Escribir el marcado tú

Omite columns y la tabla dibuja sus hijos en su lugar, así que una tabla con fila de pie o con cabeceras agrupadas se puede construir a mano y aun así recibir el estilo y el contenedor con scroll.

Props

PropTipoPor defectoDescripción
columnsTableColumn[]—{ key, header, align, render } por columna. Omítelo para escribir las filas a mano.
rowsobject[][]Un objeto por fila.
captionChild—Un título sobre la tabla.
stripedbooleanfalseAlterna bandas en las filas.
hoverbooleanfalseResalta la fila bajo el puntero.
densebooleanfalseRelleno de celda más ajustado.