Saltar al contenido
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
Primeros pasosWebReactSwiftUIJetpack ComposeFlutterReact NativePreajustesTu propio sistema de diseñoUsar Tanrei con agentes de IAMarcaTokens de diseñoSistemas de diseño y personalizaciónTheme StudioColorTipografíaIconosFormaElevaciónMovimientoEstados de interacciónComportamientoDiseño de páginaAccesibilidadTextosTokens de colorTokens de la escala tipográficaTokens de formaTokens de elevaciónTokens de movimientoTokens de estadoTokens de espacio y de medidaComponentesBotónBotón de acción principalBotón segmentadoGrupo de botonesBotón con menúBarra de herramientasBotón de copiarCasilla de verificaciónBotón de opciónInterruptorChipMenúControl deslizanteCampo de control deslizanteCalendarioDateTimePickerGrupo de camposGrupo de botones de opciónGrupo de casillas de verificaciónSelectCampo de selecciónBarra de menúsSubida de archivosSelector de archivos AvanzadoZona de soltar archivosEntrada de texto AvanzadoCampo de textoCampo de chipsCampoCuadro combinado AvanzadoCampo de cuadro combinadoEntrada numérica AvanzadoCampo numéricoEntrada de color AvanzadoCampo de colorEntrada de código de un solo uso AvanzadoCampo de código de un solo usoEntrada de texto incrustada AvanzadoCuadro de redacción de chatTarjetaDivisorCapa modal AvanzadoDiálogoListaFila de listaHoja inferiorHoja lateralCarruselBloque de códigoTablaPopoverPasosAcordeónLista de descripcionesFiguraEstructura de la aplicaciónCabecera de páginaVista de árbolLínea de tiempoVista previa de imágenesTirador de tamañoMarco redimensionableÁrea de desplazamientoInsigniaIndicador de progresoMedidorContador animadoIndicador de cargaSnackbarTooltipAtajo de tecladoTagCódigo en líneaCalloutAvatarEstado vacíoGrupo de tagsEsqueletoElemento de archivoMuestraTexto en marquesinaPestañasVista de pestañasBarra de la aplicaciónBarra de navegaciónPanel de navegaciónBúsquedaEnlacePaginaciónEnlaces anterior y siguienteIndicador de pasosRuta de navegaciónTabla de contenidoTarjeta de enlaceMenú FABEstadoCómo se construye TanreiCómo contribuirLicencia y atribución
InicioComponentesTabla

Tabla

Presenta datos que tienen filas y columnas, con columnas con nombre y, de forma opcional, encabezados de fila, una leyenda y filas de pie. Se desplaza dentro de su propia caja cuando no cabe. El encabezado de una columna puede pedir las filas en su orden y las filas se pueden elegir; la tabla nunca ordena por sí misma.

content_copyVer en Markdown
Categoría
Contención
Tokens
comp.table
Nivel
Base

Descripción general

Web
tanrei-table
schedule
SwiftUI
TanreiTable
radio_button_unchecked
Jetpack Compose
TanreiTable
radio_button_unchecked
Flutter
TanreiTable
radio_button_unchecked
React Native
Table
radio_button_unchecked

Generado a partir de docs/status/status.json, specs/table.spec.json

Encabezados de fila, números al final y una fila de pie

Componentes por estado y plataforma
Estado Web SwiftUI Compose Flutter
Planificado 2 6 6 6
Alpha 31 30 30 29
Beta 4 1 1 2
Total 37 37 37 37

Una tabla es para datos que tienen dos dimensiones: una lista de precios, una comparación de planes, una lista de ajustes con sus tipos y sus valores predeterminados, el estado de varias cosas en varias plataformas, los registros con los que trabaja una persona. El encabezado de una columna puede pedir las filas en su orden, una tabla sin filas muestra tu estado vacío, y una línea de estado bajo las filas contiene tu frase y tu paginación. Las filas se pueden elegir con una casilla de verificación cada una, o una sola fila con un botón de opción, y una tabla que espera sus filas dice que está ocupada. La tabla nunca ordena, filtra, obtiene datos ni pagina por sí misma: dibuja las filas que se le dan, en el orden en que se le dan. No cambia el tamaño de las columnas.

En la web es <tanrei-table> alrededor de tu propia <table> nativa. El elemento no tiene shadow root y a la tabla nunca se le da otro display, así que un rastreador, copiar y pegar en una hoja de cálculo y la navegación por tablas de un lector de pantalla se encuentran con una tabla HTML corriente. Las demás plataformas todavía no la han implementado.

Uso

<link rel="stylesheet" href="@tanrei/web/table.css" />

<tanrei-table row-headers>
  <table>
    <caption>Visitas por región</caption>
    <thead>
      <tr>
        <th scope="col">Región</th>
        <th scope="col" data-align="end" data-wrap="false">Enero</th>
      </tr>
    </thead>
    <tbody>
      <tr><th scope="row">Islas</th><td data-align="end">212</td></tr>
    </tbody>
    <tfoot>
      <tr><th scope="row">Todas las regiones</th><td data-align="end">6846</td></tr>
    </tfoot>
  </table>
</tanrei-table>

<script type="module">
  import '@tanrei/web/components/table';
</script>
import { Table } from '@tanrei/react';
import '@tanrei/web/table.css';

<Table density="compact" striped label="Tokens de la tabla">
  <table>
    <thead>
      <tr><th scope="col">Token</th><th scope="col">Tipo</th></tr>
    </thead>
    <tbody>
      {tokens.map((token) => (
        <tr key={token.name}><td data-wrap="false">{token.name}</td><td>{token.type}</td></tr>
      ))}
    </tbody>
  </table>
</Table>

@tanrei/web/table.css dibuja toda la tabla antes de que se ejecute ningún script. El elemento añade por sí solo la misma hoja de estilos a la página, así que una página que solo carga el script también obtiene una tabla dibujada, un momento después.

Qué escribes tú

Parte En la web
Una columna Un <th scope="col"> en la única fila de <thead>. Su texto es el encabezado.
La alineación de una columna data-align="start", "center" o "end" en la celda de encabezado. end es para números que se comparan.
Una columna cuyo texto no salta de línea data-wrap="false" en la celda de encabezado, para identificadores, fechas y números.
Una fila Un <tr> en <tbody>. Una fila de pie es un <tr> en <tfoot>.
Una celda Un <td>. Contiene texto, un componente o nada.
Un encabezado de fila Un <th scope="row"> como primera celda, con row-headers en el elemento.
El nombre Un <caption>, o label en el elemento cuando un encabezado situado sobre la tabla ya le da nombre.
Una columna que se puede ordenar aria-sort="none", "ascending" o "descending" en la celda de encabezado, y un <button type="button"> alrededor de su texto. data-value en la celda es el nombre que lleva el evento sort.
Sin filas Un <tr data-part="empty"> con un solo <td colspan> en <tbody>, que contiene tu estado vacío.
La línea de estado Un <div data-part="status"> después de la tabla, que contiene un <p role="status"> y tu paginación. status en el elemento escribe el párrafo donde tú no escribes ninguno.
Filas que se pueden elegir selection="multiple" en el elemento, o selection="single" donde se elige una fila como mucho, y una primera celda <td data-part="selection"> en cada fila.
Una fila que se puede elegir data-value en el <tr>, y un <tanrei-checkbox label-hidden> en su celda de selección, o un <tanrei-radio-button label-hidden> con single. data-selected en el <tr> dice que está elegida.
La casilla de verificación de todas las filas Un <tanrei-checkbox label-hidden> en la celda de selección de la fila de encabezado. select-all-label en el elemento le da nombre. Con single esa celda queda vacía.
Una tabla en carga loading en el elemento.
Una fila de marcador de posición, para una fila que no ha llegado Un <tr data-placeholder aria-hidden="true">, con las celdas vacías o con un marcador de posición de esqueleto en cada una.

El elemento lleva la alineación y el salto de línea de una columna desde su celda de encabezado a todas las celdas de la columna. Para que se dibujen antes de que se ejecute el script, repite data-align y data-wrap en cada celda, como hacen las demos de esta página.

Ordenación

Pulsa un encabezado: la tabla pide, y esta página ordena las filas

Productos
Nota
Báscula 0 29.50
Hervidor 15 38.00 Inducción y gas
Molinillo manual 8 64.00
Papel de filtro 120 4.50 Se vende en paquetes de cien

Una columna que se puede ordenar es una celda de encabezado que dice en qué orden están las filas, con tu propio botón alrededor de su texto:

<th scope="col" aria-sort="ascending" data-value="name"><button type="button">Nombre</button></th>
<th scope="col" aria-sort="none" data-value="price" data-align="end"><button type="button">Precio</button></th>

La hoja de estilos convierte toda la celda de encabezado en el botón y dibuja la marca a partir de aria-sort: una flecha de doble sentido mientras las filas no están en el orden de la columna, una flecha hacia arriba mientras ascienden por ella y una flecha hacia abajo mientras descienden. Tú no escribes ningún icono. Carga @tanrei/web/icons.css y la fuente de iconos, porque las marcas son glifos del conjunto de iconos.

La tabla nunca ordena. Una pulsación emite sort y no cambia nada en la pantalla; tú ordenas tus filas y escribes el nuevo aria-sort:

table.addEventListener('sort', (event) => {
  const { column, index, direction } = event.detail; // 'price', 2, 'ascending' | 'descending'
  showRows(sortedBy(column, direction));              // tus datos, tu comparación o tu servidor
  for (const th of headerCells) th.setAttribute('aria-sort', th.dataset.value === column ? direction : 'none');
});

direction es lo que pidió la persona: ascending, o descending donde las filas ya ascienden por esa columna. No hay una tercera pulsación que quite el orden. No tienes que dar el orden que se pidió: puedes poner las filas en otro orden, o dejarlas sin ningún orden.

Para una tabla que ordena el servidor, pon un enlace en lugar del botón: <a href="?sort=price&order=asc">Precio</a>, a la misma tabla en el otro orden. Funciona sin script. En ese caso sort se emite antes de que el navegador siga el enlace, y event.preventDefault() conserva la página.

Sin filas

Sin filas: tu estado vacío bajo la fila de encabezado

Productos que coinciden
Nombre Existencias Precio
search_offQuita un filtro o busca una palabra más corta.

Escribe una sola fila con una sola celda que abarque las columnas, y pon en ella tu estado vacío:

<tbody>
  <tr data-part="empty">
    <td colspan="3"><tanrei-empty-state headline="Ningún producto coincide con estos filtros">…</tanrei-empty-state></td>
  </tr>
</tbody>

La tabla le da espacio por encima y por debajo y la anchura de su caja, y lo mantiene a la vista mientras las columnas se desplazan en horizontal. No añade ningún texto propio. Sin esa fila, una tabla sin filas es solo su encabezado.

La línea de estado

Tu frase y tu paginación, bajo las filas

Versiones, de la más reciente a la más antigua
Versión Publicada Cambios
2.4.0 12 de mayo Las tablas mantienen su encabezado a la vista.
2.3.2 28 de abril Una corrección para las fechas escritas con años de dos cifras.
2.3.1 20 de abril Una corrección para un menú que se abría fuera de la pantalla.
2.3.0 2 de abril Hojas laterales y un anillo de foco más discreto.
2.2.1 18 de marzo Una corrección para un tooltip que se quedaba abierto.
2.2.0 4 de marzo Diseños de derecha a izquierda.

1–6 de 42 versiones

61224

Después de la tabla, dentro del elemento, escribe la línea de estado: tu frase en un párrafo que es un estado (role="status"), y tu paginación.

<tanrei-table>
  <table>…</table>
  <div data-part="status">
    <p role="status">1–20 de 132</p>
    <tanrei-pagination label="Páginas de los productos" variant="compact" size="small" page="1" count="7" previous-label="Página anterior" next-label="Página siguiente" count-text="Página {page} de {count}"></tanrei-pagination>
  </div>
</tanrei-table>

La frase queda en el borde de inicio y la paginación en el de fin; donde las dos no caben una al lado de la otra, la paginación pasa debajo de la frase. La línea de estado no se desplaza en horizontal con las columnas, y se queda en la parte inferior de una tabla cuyas filas se desplazan dentro de su propia caja. La tabla no cuenta nada ni escribe ningún texto: «1–20 de 132» es tuyo, en tu idioma. Cuando cambias el texto del párrafo, se le dice a la tecnología de apoyo de forma cortés. El atributo status del elemento escribe ese párrafo por ti donde tu marcado no tiene ninguno. Un párrafo que has escrito tú es el que se lee, y entonces el atributo no cambia nada.

Lo que queda en el borde de fin es una paginación de tamaño small: compacta, como aquí, o con números donde la gente va a una página por su número. Donde la gente elige cuántas filas tiene una página, pon antes de la paginación un select para eso, los dos en un elemento que tú dispones; el rango de filas que se muestra es tu frase, no de la paginación. Los enlaces anterior y siguiente no van aquí: llevan a páginas con título, no a las filas de una lista. La tabla solo coloca lo que le das: cuando cambia la página, cambia tú las filas y la frase.

Elegir filas

Elige filas: la tabla guarda la selección, y esta página la cuenta

Productos para reponer
Nombre Existencias Precio
Papel de filtro 120 4.50
Molinillo manual 8 64.00
Hervidor 15 38.00
Báscula 0 29.50

Seleccionados: 2 de 4

Define selection="multiple" en el elemento y empieza cada fila con una celda de selección. En la fila de encabezado, la celda contiene la casilla de verificación de todas las filas. En una fila del cuerpo contiene la casilla de esa fila, y la fila lleva en data-value el nombre por el que conoces el registro:

<tanrei-table selection="multiple" row-headers select-all-label="Seleccionar todos los productos">
  <table>
    <caption>Productos para reponer</caption>
    <thead>
      <tr>
        <td data-part="selection"><tanrei-checkbox label-hidden></tanrei-checkbox></td>
        <th scope="col">Nombre</th>
        <th scope="col" data-align="end">Precio</th>
      </tr>
    </thead>
    <tbody>
      <tr data-value="grinder" data-selected>
        <td data-part="selection"><tanrei-checkbox label-hidden checked></tanrei-checkbox></td>
        <th scope="row">Molinillo manual</th>
        <td data-align="end">64.00</td>
      </tr>
    </tbody>
  </table>
  <div data-part="status"><p role="status">Seleccionados: 1 de 4</p></div>
</tanrei-table>

La tabla guarda la selección. Cuando una persona cambia una casilla de verificación, el elemento escribe data-selected en esa fila, mantiene al día la casilla de la fila de encabezado y emite selectionchange una sola vez, con los valores de todas las filas elegidas, en el orden de las filas:

table.addEventListener('selectionchange', (event) => {
  if (!(event instanceof CustomEvent)) return;       // mira la nota de más abajo
  const { values } = event.detail;                   // ['grinder', 'scale']
  // tu frase, en tu idioma, en el párrafo que escribiste
  table.querySelector('[role="status"]').textContent = `Seleccionados: ${values.length} de ${total}`;
});
  • Para elegir tú una fila, escribe data-selected en su <tr>; quita el atributo y la fila deja de estar elegida. El elemento marca o desmarca la casilla de verificación de la fila y no emite nada.
  • Que una fila esté elegida está escrito en la propia fila (data-selected). Las filas que vuelves a dar en otro orden siguen elegidas o sin elegir, y una fila que la tabla recibe por primera vez solo está elegida donde ella misma lo dice.
  • Una fila sin data-value no se puede elegir. Deja vacía su celda de selección, como en una fila de pie.
  • La casilla de verificación de la fila de encabezado está marcada mientras todas las filas están elegidas, indeterminada mientras lo están algunas y deshabilitada mientras no hay ninguna fila que elegir. Representa las filas que se le dieron a la tabla, nunca las filas de otras páginas.
  • La columna de selección no es una columna de datos. row-headers y sticky-first-column se refieren a la columna que la sigue, y el index de un evento sort no la cuenta.
  • Una pulsación en cualquier punto de una fila que se puede elegir cambia su casilla de verificación, igual que una pulsación en la casilla. Un enlace, un botón o un campo de sus celdas conservan su propia pulsación, y un arrastre, una selección de texto, el segundo clic de un doble clic o un clic con una tecla modificadora no cambian nada. La fila muestra el paso del cursor y la pulsación como un todo; el teclado sigue en la casilla.

El navegador tiene un evento propio llamado selectionchange, para un cambio en la selección de texto, y un campo de texto dentro de una celda lo envía a través de la tabla. El evento de la tabla es un CustomEvent que lleva detail.values; el del navegador no lo es. Compruébalo, como hace el ejemplo.

Para una página que debe funcionar sin script, usa casillas de verificación nativas. La hoja de estilos dibuja por sí sola como elegida una fila cuya casilla está checked, y las casillas se envían con tu formulario:

<tr data-value="grinder">
  <td data-part="selection"><input type="checkbox" name="product" value="grinder" checked aria-label="Seleccionar Molinillo manual" /></td>
  <th scope="row">Molinillo manual</th>
</tr>

Elegir una fila

Elegir una fila: un botón de opción en cada fila, ninguno en el encabezado

Plan al que cambiar
Plan Puestos Al mes
Inicial 3 0.00
Equipo 20 48.00
Empresa 100 180.00

Donde una persona elige un registro (el plan al que cambiar, la dirección de envío, la compilación con la que comparar), define selection="single". La celda de selección de cada fila del cuerpo contiene un botón de opción en lugar de la casilla de verificación, y la celda de selección de la fila de encabezado queda vacía, porque ningún control representa a todas las filas:

<tanrei-table selection="single" row-headers>
  <table>
    <caption>Plan al que cambiar</caption>
    <thead>
      <tr><td data-part="selection"></td><th scope="col">Plan</th><th scope="col" data-align="end">Puestos</th></tr>
    </thead>
    <tbody>
      <tr data-value="team" data-selected>
        <td data-part="selection"><tanrei-radio-button label-hidden name="plan" checked></tanrei-radio-button></td>
        <th scope="row">Equipo</th>
        <td data-align="end">20</td>
      </tr>
    </tbody>
  </table>
</tanrei-table>
  • Elegir una fila suelta la que estaba elegida. selectionchange lleva un solo valor: { values: ['team'] }.
  • Pulsar la fila elegida, o su botón de opción, no cambia nada. Un botón de opción no se desmarca al pulsarlo otra vez. Donde una persona pueda volver a no tener ninguna fila elegida, pon un control propio para ello y quita data-selected de la fila.
  • Los botones de opción de una tabla comparten un nombre, así que forman un grupo: una sola parada en el orden del foco, y las teclas de flecha llevan la elección de una fila a otra. Dales tu propio nombre, que es el que envía un formulario; el elemento da uno a los que no lo tienen.
  • Si marcas más de una fila con data-selected, queda elegida la que marcaste la última y, de las marcadas a la vez, la primera. Una compilación de depuración notifica las filas marcadas a la vez.
  • Todo lo demás es como con las casillas de verificación: pulsar en cualquier parte de una fila la elige, data-selected lo escribes tú, y un <input type="radio" name="…"> nativo funciona sin script.

Carga

En carga: filas de marcador de posición, y la línea de estado lo dice

Pedidos de esta semana
Cliente Realizado Total

Cargando

Define loading mientras obtienes las filas. La tabla se expone como ocupada y, entretanto, dibuja lo que le das. No obtiene nada ni espera nada: está en carga mientras tú lo digas.

<tanrei-table loading>
  <table>
    <thead>…</thead>
    <tbody>
      <tr data-placeholder aria-hidden="true">
        <td><span class="tanrei-skeleton-placeholder"></span></td>
        <td data-align="end"><span class="tanrei-skeleton-placeholder" style="inline-size: 3rem; margin-inline-start: auto"></span></td>
      </tr>
    </tbody>
  </table>
  <div data-part="status"><p role="status">Cargando</p></div>
</tanrei-table>
  • Para una primera carga, da filas de marcador de posición: <tr data-placeholder aria-hidden="true">. Una fila de marcador de posición tiene al menos una línea de texto de alto, no tiene casilla de verificación y queda oculta para la tecnología de apoyo.
  • Deja vacías las celdas de una fila de marcador de posición, o pon en cada una un marcador de posición de esqueleto (class="tanrei-skeleton-placeholder", de @tanrei/web/skeleton.css). Un marcador de posición es tan alto como una línea del texto de la celda, así que la fila es tan alta como la fila que la sustituye. Aquí basta con la clase: la tabla ya dice que está ocupada.
  • Para otra página o para otro orden, conserva las filas que la tabla tenía y define loading. Las filas que una persona estaba leyendo se siguen pudiendo leer.
  • Nada se atenúa y nada se deshabilita. Los encabezados y las casillas de verificación funcionan mientras la tabla carga.
  • Mientras loading está definido, la fila del estado vacío no se dibuja, aunque no haya ninguna otra fila.

Markdown

Un renderizador de Markdown escribe una <table> sin más. Envuélvela durante la compilación; Tanrei no mueve tu contenido en tiempo de ejecución. Con rehype es un paso pequeño:

import { visit, SKIP } from 'unist-util-visit';

/** Envuelve cada tabla del documento en <tanrei-table>. */
export function rehypeTanreiTable() {
  return (tree) =>
    visit(tree, 'element', (node, index, parent) => {
      if (node.tagName !== 'table' || parent?.tagName === 'tanrei-table') return;
      parent.children[index] = { type: 'element', tagName: 'tanrei-table', properties: {}, children: [node] };
      return SKIP;
    });
}

El align="right" que un renderizador pone en una celda se lee como end, y align="left" como start, de modo que «los números a la derecha» pasa a ser «los números al final» en una página de derecha a izquierda.

Especificaciones

Compacta y con franjas, para una tabla que se recorre con la vista

Token Alias de Tipo
comp.table.separation sys.behavior.separation number
comp.table.divider.color sys.color.outline-variant color
comp.table.divider.thickness sys.border.hairline dimension
comp.table.row.gap sys.space.space50 dimension
comp.table.stripe.container.color sys.color.surface-container-low color
comp.table.caption.gap sys.space.space100 dimension

Más ancha que su caja: se desplaza dentro de ella, y la primera columna se queda

Visitas por región
Región Enero Febrero Marzo Abril Mayo Junio Julio
Región norte 1204 1310 1288 1402 1396 1450 1512
Región central 3020 2985 3110 3204 3190 3275 3301
Región sur 870 902 915 899 940 968 1002
Islas 212 220 198 240 251 263 270

Una altura que viene del diseño: las filas se desplazan, y el encabezado se queda

Versiones
Versión Publicada Cambios
2.4.0 12 de mayo Las tablas mantienen su encabezado a la vista.
2.3.2 28 de abril Una corrección para las fechas escritas con años de dos cifras.
2.3.1 20 de abril Una corrección para un menú que se abría fuera de la pantalla.
2.3.0 2 de abril Hojas laterales y un anillo de foco más discreto.
2.2.1 18 de marzo Una corrección para un tooltip que se quedaba abierto.
2.2.0 4 de marzo Diseños de derecha a izquierda.
2.1.0 11 de febrero El selector de fecha y hora.

Anatomía

Parte Descripción
1 container The table's box: as wide as its layout gives it, as tall as its rows unless the layout limits it. It has no fill, edge or corner of its own. When the columns or the rows do not fit, it is the box they scroll in, and then the one part of the table that takes focus.
2 caption · opcional The name of the table, drawn above its first row at the start edge. It does not scroll sideways with the columns.
3 header-row · opcional The first row: one header cell for each column. It can stay in view while the rows scroll under it.
4 header-cell · opcional The name of a column, in a label style and a quieter color than the data.
5 sort-button · opcional The whole header cell of a column that can be sorted, as one button: the column's name and its mark. It carries hover, focus and press, and asks the product for the rows in the column's order.
6 sort-mark · opcional The glyph beside the name of a column that can be sorted: a two-way arrow while the rows are not in its order, an arrow up while they ascend by it, an arrow down while they descend.
7 row One record: a cell for each column, as tall as its tallest cell. A row that can be chosen is, for a pointer, one target that presses its checkbox or radio button, with its own hover and press (TBL-58, TBL-59).
8 row-header · opcional The first cell of a row where the first column names its rows: the cell style at its emphasized weight. The first column can stay in view while the others scroll under it.
9 selection-cell · opcional The cell before the first column of a table whose rows can be chosen: in a body row the row's checkbox, or its radio button where one row at most is chosen; in the header row the checkbox for every row, or nothing where one row at most is chosen; in a footer row nothing.
10 cell One value: text that wraps inside its column, or a component of the product's (a tag, a link, inline code). It may be empty.
11 divider · opcional The one hairline between two rows, under the header row and above the footer rows, where rows are told apart by a line; also the line along the inner edge of a first column that stays in view, and along an edge of the box beyond which there is more to scroll to.
12 stripe · opcional A quiet fill on every second body row.
13 selected-row · opcional The fill of a body row that is chosen, across its whole width, in place of a stripe.
14 placeholder-row · opcional A body row that stands for a record that has not arrived: as tall as a row of one line, holding what the product puts in its cells (a Skeleton), and no checkbox or radio button.
15 empty · opcional What stands in the place of the body rows of a table that has none: the product's own content (an Empty state), as wide as the table's box, under the header row.
16 footer-row · opcional A row after every body row that sums the columns up or notes something about them: the cell style at its emphasized weight.
17 status · opcional One line under the last row, outside the columns: the product's sentence about what the table shows now (the range of rows shown, how many are chosen) at the start edge, and its pagination at the end edge. It does not scroll sideways with the columns.

Propiedades

Propiedad Tipo Valor predeterminado Descripción
caption string "" The name of the table, drawn above it ("Response times by region"). It is also what assistive technology calls the table and its scroll region. Prefer it to label wherever the page does not already name the table in sight.
label string "" The table's accessible name where no caption is drawn, because a heading just above it already says what it is. Not drawn. Ignored where caption is given.
density regular | compact "regular" How close the rows are. Regular (a 46 row in the default system) is for a table a person reads: a few rows, cells that wrap. Compact (a 32 row, smaller text) is for a table a person scans: hundreds of rows, identifiers and values. A design system may add values.
separation auto | divider | gap "auto" How a row is told from the row next to it. divider: one hairline between them. gap: empty space and no line. Auto takes comp.table.separation, the design system's answer (divider in the default system).
striped boolean false Fills every second body row with a quiet color, for a wide table of many similar rows where the eye loses its row on the way across. It can be combined with either separation.
rowHeaders boolean false Says that the first column names its rows (a component, a region, a plan), so its cells are drawn and exposed as row headers. False for a table whose rows are named by no single column.
stickyHeader boolean false Keeps the header row at the top of the table's box while its rows scroll under it. It has an effect only where the layout limits the table's height, so that the rows scroll in the table's own box.
stickyFirstColumn boolean false Keeps the first column at the start edge while the other columns scroll sideways under it, for a wide table whose first column says what each row is. Released by itself where the column would take more than half of the box.
selection none | single | multiple "none" Whether rows can be chosen, and how many. multiple puts a checkbox before the first column of every body row that has a value, and one in the header row that stands for all of them. single puts a radio button there instead and nothing in the header row: one row at most is chosen, and a person moves the choice from row to row but does not take it back (TBL-60). none draws no such column and ignores a row's selected.
selectAllLabel string "" The accessible name of the checkbox in the header row that chooses every row ("Select all rows"). Not drawn; read only while selection is multiple. From the product's words where the instance gives none.
sortedAscendingLabel string "" What a platform that has no sort state of its own says after the name of the column the rows ascend by ("sorted ascending"). Not drawn. A platform that exposes the direction itself (the web) does not read it.
sortedDescendingLabel string "" What a platform that has no sort state of its own says after the name of the column the rows descend by ("sorted descending"). Not drawn. A platform that exposes the direction itself (the web) does not read it.
loading boolean false Says that the product is fetching the rows — the first ones, another page of them, or the same ones in another order. The table is exposed as busy and draws the rows it is given meanwhile: the ones it had, or placeholder rows. It fetches nothing itself.
status string "" The product's sentence about what the table shows now ("1–20 of 132", "3 of 132 selected", "Loading"), drawn under the last row and told politely to assistive technology when it changes. The table counts nothing and writes no word of its own. Empty draws no status text.

Slots

Slot Descripción
empty What the table draws in the place of its body rows while it has none and is not loading: the product's Empty state, or its own sentence. The table gives it the box's width and room above and below, and does nothing else to it. A fixture's string is drawn as plain text in its place.
pagination What stands at the end edge of the status line: one element, the product's Pagination — at size small, and compact where the product does not page by number — or a group the product lays out of a Select for the rows per page and that Pagination, as the table paginations of Carbon and MUI are. The range of rows shown ("1–20 of 132") is the status text, not the slot's. It is never Previous and next links, which lead to titled pages and not to the rows of one list. The table places it and does nothing else to it. A fixture's string is drawn as plain text in its place.

Eventos

Evento Descripción
sort The user pressed the header of a sortable column; carries column (the column's value), index (its place among the columns of data, from 0) and direction (ascending or descending: the one asked for, TBL-34). The table changes nothing: the product orders its rows and sets the column's sort.
selectionChange The user changed which rows are chosen — by a row's checkbox or radio button, by a press on the row, by the checkbox for every row, or by an arrow key on a radio button; carries values: the values of the chosen rows after the change, in row order. While selection is single it holds the one chosen row's value.

Elementos

Los tipos de elemento que el componente repite. Un fixture enumera sus elementos en orden; cada plataforma nombra cada tipo según su propio estilo.

column

One column of the table: the text of its header cell, how its cells are aligned and wrapped, and whether its header asks for the rows in its order.

Propiedad Tipo Valor predeterminado Descripción
header string "" The text of the column's header cell ("Price"). Empty only where the table has no header row at all, and then for every column.
align start | center | end "start" Where the content of the column's cells sits, header and footer cells included. start for text. end for numbers that are compared, so that their digits line up. center for a mark or a single word that is the same kind of answer in every row.
wrap boolean true Whether the text of the column's cells may wrap. False keeps every cell on one line, for identifiers, dates, numbers and short values; the column is then as wide as its widest cell.
sortable boolean false Says that the rows can be put in this column's order: its header cell is a button that asks for it. The table never orders rows itself. It needs a header: a sortable column without one is drawn as a plain column.
sort none | ascending | descending "none" The order the rows are in by this column, as the product has put them: the header draws it and exposes it. The product sets it after it has ordered its rows; one column at a time is not none.
value string "" The name the product knows the column by ("price"), carried by the sort event. Not drawn. Empty: the event carries the column's index alone.
Especificación Web SwiftUI Jetpack Compose Flutter React Native
Elemento th TanreiTableColumn TanreiTableColumn TanreiTableColumn TableColumn
header · Propiedad scope header header header header
align · Propiedad data-align alignment align align align
wrap · Propiedad data-wrap wraps wrap wrap wrap
sortable · Propiedad button isSortable sortable sortable sortable
sort · Propiedad aria-sort sort sort sort sort
value · Propiedad data-value value value value value
Notas A <th scope="col"> in the <thead> row; its text is the header. data-align="end" and data-wrap="false" on that <th> reach every cell of the column: the element writes the positions of such columns on the wrapper (data-columns-end, data-columns-center, data-columns-nowrap) and one rule per position places the cells, so no cell is written to; without script, and from a Markdown renderer, the same attributes (or the renderer's own align attribute, where left and right are read as start and end) sit on each cell. A sortable column: the <th> holds the product's own <button type="button"> (or, for a table ordered by the server, an <a href> to the same table in the other order) around the header's text, and carries aria-sort="none", "ascending" or "descending"; the stylesheet draws the mark from that attribute, so the product writes no glyph. data-value on the <th> is the column's value. An entry of the columns prop.

row · grupo: contiene elementos

One row of the table: a cell for each column, in the order of the columns; a record that can be chosen where the table's selection says so.

Propiedad Tipo Valor predeterminado Descripción
footer boolean false Says that the row is a footer row: drawn after every body row, in the emphasized weight, never striped. For sums, averages and notes about the columns.
value string "" The name the product knows the record by, carried by selectionChange. Not drawn. A body row without a value cannot be chosen: it has no checkbox or radio button.
selected boolean false Whether the row is chosen. Read only while the table's selection is single or multiple and the row has a value; while it is single, one row at most is chosen (TBL-60).
selectLabel string "" The accessible name of the row's checkbox or radio button ("Select Islands"): words about this one record, so the instance's. Not drawn. Empty: it is named by the row's header cell where rowHeaders is true.
placeholder boolean false Says that the row stands for a record that has not arrived. It is drawn as a body row at least one line tall, holds what the product puts in its cells (a Skeleton) or nothing, has no checkbox or radio button and is hidden from assistive technology.
Especificación Web SwiftUI Jetpack Compose Flutter React Native
Elemento tr TanreiTableRow row TanreiTableRow Table.Row
footer · Propiedad tfoot isFooter footer footer footer
value · Propiedad data-value value value value value
selected · Propiedad data-selected isSelected selected selected selected
selectLabel · Propiedad aria-label selectLabel selectLabel selectLabel selectLabel
placeholder · Propiedad data-placeholder isPlaceholder placeholder placeholder placeholder
Notas A <tr>; a footer row is a <tr> inside <tfoot>. A row that can be chosen has data-value and a first cell <td data-part="selection"> holding the product's checkbox — <tanrei-checkbox label-hidden label="…">, or a native <input type="checkbox" aria-label="…"> that also works in a form without script; selectLabel is that checkbox's own name. data-selected on the <tr> draws the row as chosen; the element keeps it in step with the checkbox, and the stylesheet also reads a native checkbox that is checked, so a row is drawn chosen before and without script. While selection="single" the selection cell holds a radio button instead — <tanrei-radio-button label-hidden name="…" label="…">, or a native <input type="radio" name="…" aria-label="…"> — the radio buttons of one table share one name (the element gives one to those that have none, so that they form one group), and the header row's selection cell is empty. A placeholder row is <tr data-placeholder aria-hidden="true">. A call in the table's scope: row(footer = false) { cell("…") }.

cell · grupo: contiene elementos

One value of a row: plain text, a component of the product's, or nothing.

Slot Descripción
content What the cell holds in place of plain text: a tag, a link, inline code, a button, a picture, any view of the product's. The table places it and styles nothing inside it (TBL-15).
Especificación Web SwiftUI Jetpack Compose Flutter React Native
Elemento td TanreiTableCell cell TanreiTableCell Table.Cell
Notas A <td>, or a <th scope="row"> for a row header; its children are the content. A cell takes a string or a view builder. cell(text) or cell { content } in a row's scope. TanreiTableCell.text("…") or TanreiTableCell(child: …). Its children are a string or elements.

tag

A tag placed in a cell: the Tag component, as it is drawn anywhere else.

Propiedad Tipo Valor predeterminado Descripción
label string "" The tag's text.
variant tonal | outlined | filled "tonal" The tag's look.
status default | info | warning | error | success "default" What the tag reports.
size small | medium "medium" The tag's size; small in a table cell, so that it does not make its row taller.
Especificación Web SwiftUI Jetpack Compose Flutter React Native
Elemento tanrei-tag TanreiTag TanreiTag TanreiTag Tag
label · Propiedad label title label label label
variant · Propiedad variant variant variant variant variant
status · Propiedad status status status status status
size · Propiedad size size size size size

Estados

Comportamiento

Requisitos en formato EARS. Cada implementación de plataforma se prueba contra estos identificadores. El texto de los requisitos se muestra tal como está en la especificación, en inglés.

ID Requisito
TBL-1 THE table SHALL draw, from top to bottom, its caption where it has one, one header row holding one header cell for each column, its body rows in the order given, and its footer rows after every body row, each row holding one cell for each column in the order of the columns; THE table SHALL be at least as wide as the layout gives it, SHALL draw no fill, no edge, no corner and no elevation of its own around its rows, and SHALL bring no outer space of its own, so that it is spaced by what holds it.
TBL-2 THE table SHALL size every column from its content: a column SHALL never be narrower than the widest thing in it that cannot break — a word, a number, a cell of a column whose wrap is false, a component — with the cell's inline paddings; WHILE the columns at those least widths fit the table's box THE table SHALL share the box's width among them in proportion to their content, so that the table fills its box and a column of long text takes what the short columns leave; THE table SHALL NOT give a column a width in a unit of its own, and a width the product gives a column SHALL replace what the content would give it.
TBL-3 THE table SHALL wrap the text of a cell inside its column, breaking lines by the rules of the text's language, and SHALL never cut it or end it with an ellipsis; a run with no break opportunity (an identifier, an address) SHALL NOT be broken inside and widens its column instead (TBL-2, TBL-17); WHERE a column's wrap is false THE table SHALL keep every cell of that column, its header cell included, on one line.
TBL-4 THE table SHALL pad every cell at the top and bottom by comp.table.cell.padding-block.regular and at the start and end by comp.table.cell.padding-inline.regular while density is regular, and by comp.table.cell.padding-block.compact and comp.table.cell.padding-inline.compact while density is compact, the first and the last cell of a row included; SHALL set a cell's text in comp.table.cell.text.typescale.regular or comp.table.cell.text.typescale.compact by density, in comp.table.cell.text.color, with digits in figures of equal width so that the numbers of a column line up; SHALL make a row as tall as its tallest cell and no taller, with no height of its own; and SHALL align the cells of a row to the top, so that the first lines of a row share a baseline.
TBL-5 THE table SHALL set a column's header cell in comp.table.header.text.typescale.regular or comp.table.header.text.typescale.compact by density, in comp.table.header.text.color, with the paddings of a cell (TBL-4), and SHALL fill the header row with comp.table.header.container.color; the header cells of a row SHALL align to the bottom, so that the last line of each sits next to the data it names. IF every column's header is empty THE table SHALL draw no header row and no line for it, and the table then relies on its row headers (TBL-6).
TBL-6 WHILE rowHeaders is true THE table SHALL treat the first cell of every body row and of every footer row as the header of its row: it SHALL draw that cell in the cell's text style at its emphasized weight — the style's weight plus sys.typescale.emphasis.body — in comp.table.cell.text.color, with a cell's paddings and no fill of its own, and SHALL expose it as a row header (TBL-24); WHILE rowHeaders is false THE table SHALL draw and expose the first column as cells, except that a first cell the product's own content already marks as the header of its row (on the web a th whose scope is row) SHALL be drawn and exposed as one whatever rowHeaders says: rowHeaders adds row headers and takes none away. WHILE selection is multiple or single the first column is the first column of data, after the selection column (TBL-42).
TBL-7 THE table SHALL align the content of every cell of a column, its header cell and its footer cells included, as the column's align names — start: at the start edge of the cell; end: at the end edge, for numbers that are compared digit by digit; center: in the middle, for a mark or a single word that is the same kind of answer in every row — and SHALL align every line of a wrapped cell the same way; a value of align the table does not define SHALL be treated as start.
TBL-8 WHILE the table's separation is divider (TBL-9) THE table SHALL draw one line of comp.table.divider.thickness in comp.table.divider.color across its whole width under the header row, between every two rows that follow each other, and between the last body row and the first footer row, without adding to a row's height or moving its content; it SHALL draw no line above the first row or below the last, and no line between columns.
TBL-9 THE table SHALL tell a row from the row next to it as its separation names: WHILE separation is auto, as comp.table.separation names, which follows sys.behavior.separation unless a design system sets it apart; WHILE separation is divider or gap, as that value names, whatever the tokens say; divider draws the lines of TBL-8 and gap leaves the space of TBL-10. WHEN separation, or the token it follows, changes THE table SHALL draw the other form at once, without animation, keeping its scroll position; WHEN separation names a value the table does not define THE table SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, divider, and state that in its README.
TBL-10 WHILE the table's separation is gap (TBL-9) THE table SHALL leave comp.table.row.gap of empty space under the header row, between every two rows that follow each other, and between the last body row and the first footer row, SHALL draw none of the lines of TBL-8, and SHALL leave no such space above the first row or below the last; the rows SHALL stay exposed as rows (TBL-24).
TBL-11 WHILE striped is true THE table SHALL fill every second body row — the second, the fourth and so on, counted among the body rows as they are shown — with comp.table.stripe.container.color across the row's whole width, paddings included, and SHALL fill neither the header row nor a footer row with it; the fill SHALL change nothing else about the row, and the row separation of TBL-9 SHALL be drawn as without it.
TBL-12 WHERE caption is not empty THE table SHALL draw it above the header row, at the start edge of the table's box, in comp.table.caption.text.typescale and comp.table.caption.text.color, wrapping inside the box's width, comp.table.caption.gap above the first row; the caption SHALL stay where it is while the columns scroll sideways (TBL-17) and SHALL be the table's accessible name (TBL-23).
TBL-13 WHERE a row's footer is true THE table SHALL draw it after every body row, in the order given, as a footer row: its cells in the cell's text style at its emphasized weight — the style's weight plus sys.typescale.emphasis.body — in comp.table.cell.text.color, aligned as their columns are, never striped, and exposed as rows of the table that follow its body; IF a row whose footer is false is given after a footer row THE table SHALL still draw every body row before every footer row and in debug builds report the order.
TBL-14 WHERE a cell has no content THE table SHALL draw nothing in it, keep its place and its paddings so that the cells after it stay in their columns, and expose it as an empty cell; THE table SHALL NOT put a dash, a word or any other mark of its own in it. IF a row holds fewer cells than the table has columns THE table SHALL keep the row's place and its height and leave the places of the missing cells empty at the row's end: a platform that builds its rows SHALL draw them as empty cells, and a platform that draws the product's own rows (the web) SHALL write no cell into them, so that there the row's line and a stripe end at its last cell, and in debug builds SHALL report the row; IF it holds more THE table SHALL draw only as many as it has columns and in debug builds report the row.
TBL-15 WHERE a cell holds a component or any content of the product's that is not plain text — a tag, a link, inline code, a button, a picture — THE table SHALL place it by the column's alignment (TBL-7) on the cell's first line, SHALL add nothing to it and style nothing inside it, and SHALL let pointer, touch and keyboard input reach it as it would outside the table, in the reading order of the cells.
TBL-16 THE table and its cells SHALL NOT take pointer, touch or keyboard input of their own and SHALL NOT change anything they draw under a pointer, a press or focus, and a row SHALL NOT either except as TBL-58 and TBL-59 say: no row has a focused state, is a stop in the focus order or takes a key; a row that cannot be chosen — every row of a table whose selection is none, the header row, a footer row, a placeholder row and a body row without a value — has no hovered or pressed state and is not pressed as a whole; and a cell is not a stop in the focus order. Four parts of the table take input, each by its own sentences: the header cell of a sortable column, which is a button (TBL-32 to TBL-41); the checkboxes of a table whose selection is multiple, which are the Checkbox component, and the radio buttons of a table whose selection is single, which are the Radio button component (TBL-42 to TBL-47, TBL-60, TBL-61); a body row that can be chosen, which a pointer presses as its checkbox or radio button (TBL-58, TBL-59); and the scroll region while the table scrolls (TBL-22). WHILE no column is sortable and selection is none THE table SHALL emit no event.
TBL-17 WHEN the columns at their least widths (TBL-2) are wider than the table's box THE table SHALL keep its box at the width the layout gives it and scroll its columns sideways inside that box, header row, body rows and footer rows together, beginning at the start edge; it SHALL never be wider than its layout and SHALL never make what holds it scroll sideways; a wheel, a trackpad, a drag on a touch screen and the keys of TBL-22 SHALL scroll it.
TBL-18 WHERE the layout gives the table less height than its rows need THE table SHALL scroll its rows up and down inside its own box, the caption above them as the first thing in the box, where it MAY scroll away with the rows on a platform whose caption is part of what scrolls (the web's native caption), and the status line staying at the bottom of the box (TBL-51); WHERE the layout sets no such limit THE table SHALL be exactly as tall as its caption and its rows, and SHALL set no height and no number of rows of its own.
TBL-19 WHILE stickyHeader is true and the table scrolls its rows in its own box (TBL-18) THE table SHALL keep the header row at the top of that box while the rows pass under it, filled with comp.table.sticky.container.color under comp.table.header.container.color so that nothing shows through it, and SHALL draw a line of comp.table.divider.thickness in comp.table.divider.color under it whatever the separation is while a row is under it; WHILE stickyHeader is false, or the table does not scroll its rows in its own box, THE header row SHALL scroll with the rows.
TBL-20 WHILE stickyFirstColumn is true and the table scrolls sideways (TBL-17) THE table SHALL keep the first column — its header cell, its cells and its footer cells — at the start edge of the box while the other columns pass under it, each of its cells filled with comp.table.sticky.container.color under the fill its row gives it (comp.table.header.container.color in the header row, comp.table.stripe.container.color in a striped row), and SHALL draw a line of comp.table.divider.thickness in comp.table.divider.color along its inner edge for the table's whole height while a column is under it; WHILE both stickyHeader and stickyFirstColumn hold THE first header cell SHALL stay above both. IF the first column is wider than half of the table's box THE table SHALL let it scroll with the other columns, so that a narrow window is never filled by the column that stays.
TBL-21 WHILE the table scrolls sideways and columns lie beyond an inline edge of its box THE table SHALL draw a line of comp.table.divider.thickness in comp.table.divider.color along that edge of the box for the table's whole height, and SHALL remove it when the scroll reaches that edge; at the start edge of a table whose first column is kept in view the line of TBL-20 is that line. THE table SHALL show that there is more with these lines alone: no shadow, no fade and no color that is not the divider's.
TBL-22 WHILE the table scrolls in its own box, sideways or up and down, THE table SHALL make that box one stop in the focus order, before anything in its cells that takes focus, exposed as a region that carries the table's name (TBL-23); WHILE it has keyboard focus the box SHALL draw the system's focus indicator inside its edge — sys.focus.outer-width wide in the color role focus-ring, set in from the edge by sys.focus.offset so that nothing clips it, and never animated — and the arrow keys, Page Up, Page Down, Home and End SHALL scroll it as the platform scrolls a region, the left and right arrow keys moving the view left and right in either layout direction; WHILE the table does not scroll THE table SHALL NOT be a stop in the focus order and SHALL expose no region.
TBL-23 THE table SHALL take its accessible name from caption where caption is not empty, and from label otherwise, and SHALL NOT draw label; IF both are empty THE table SHALL be exposed without a name and in debug builds report that a table has no name, and WHILE such a table scrolls in its own box that box SHALL still be a stop in the focus order (TBL-22), exposed as a region without a name, because the keyboard must be able to scroll it; IF both are given THE caption SHALL be the name and label SHALL be ignored.
TBL-24 THE table SHALL be exposed to assistive technology as a table with its number of rows and columns, its header cells as column headers, its first cells as row headers while rowHeaders is true, and every cell associated with the header of its column and, where there is one, of its row, so that a person moving cell by cell hears what each value belongs to; a platform that has no table semantics SHALL expose each row as one group in reading order and each cell with its column header before its own content. Keeping a header row or a first column in view, striping, the density and scrolling SHALL change nothing that is exposed.
TBL-25 WHEN the table's rows, columns or cells change THE table SHALL draw the new content at once, without a motion, keeping its scroll position where the content it showed still exists, and SHALL NOT announce the change by itself. The one place where the table tells a change is its status line, and what that says is the product's (TBL-52); WHEN rows are replaced because the product ordered or fetched them THE table SHALL keep keyboard focus on the sort button, the checkbox or the radio button that had it, where that control still exists.
TBL-26 THE table SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the table is shown THE table SHALL draw itself for the new direction at once, without a motion of its own, keeping its content and the column a person was looking at in view.
TBL-27 WHERE the layout direction is right-to-left THE table SHALL lay its columns out from the right, the first column at the right edge; SHALL apply paddings and the alignments start and end by the layout direction; SHALL begin its sideways scroll at the right edge; SHALL keep a first column that stays in view (TBL-20) at the right edge with its line on its left; and SHALL draw the lines of TBL-21 at the edges beyond which columns lie. Top and bottom SHALL NOT change: the header row is the first row and the footer rows the last in either direction.
TBL-28 THE table SHALL lay out the text of each cell, of each header cell and of the caption as text of its own direction, isolated from the cells beside it: it takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right name, an identifier, a number with its sign, unit or currency, a version and a date keep their own order in a right-to-left layout; THE table SHALL place that text by the column's alignment in the layout direction, not by the text's own, and SHALL NOT reverse the digits of a number: a column of numbers whose align is end is at the left edge of its cells in a right-to-left layout, with its digits written left to right.
TBL-29 WHILE the text size is enlarged THE table SHALL grow every text style with it, give up none of its paddings and none of its lines (they MAY grow with the text where the design system's units follow the text size), size its columns again from the larger content (TBL-2), wrap what can wrap and scroll in its own box where the columns no longer fit (TBL-17); it SHALL cut no content, SHALL set no fixed height on a row or a cell, SHALL lose no content when the reader raises line height, paragraph spacing, letter spacing or word spacing, and SHALL need no sideways scrolling of what holds it at a width of 320 points.
TBL-30 WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE table SHALL draw every line of TBL-8, TBL-19, TBL-20 and TBL-21 in the platform's text color and its text in that color, SHALL keep header cells, row headers and footer cells told apart by their text styles, and, because the mode removes fills, SHALL draw the lines of TBL-8 in a striped table whatever its separation is, so that its rows are still told apart.
TBL-31 WHEN density names a value that the active design system does not have THE table SHALL render with the default value's tokens, and in debug builds report the unknown value.
TBL-32 WHERE a column's sortable is true and its header is not empty THE table SHALL draw that column's header cell as one button, the sort button: its container is the whole box of the header cell, paddings included, with square corners; it holds the column's header as its label, in the header's text style (TBL-5), and the sort mark — one glyph of the system's icon set in a box of comp.table.header.icon.size, drawn through the icon helper with sys.icon.family.unselected, sys.icon.fill.unselected, the weight comp.table.header.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max — comp.table.header.gap after the label's box, at the height of the label's last line, so that a header that wraps keeps its mark at the edge of its label rather than after the last word; in a column whose align is end the mark SHALL stand before the label, so that the end of the label stays on the edge its numbers end at. The mark's box SHALL be kept whatever the column's sort is, so that a change of order moves no label and changes no column's width. IF sortable is true and header is empty THE table SHALL draw the column as one that is not sortable and in debug builds report it.
TBL-33 THE table SHALL draw the order the product sets and never make one: WHILE a sortable column's sort is none its sort button is the part sort — the label in comp.table.sort.label.color, the glyph swap_vert in comp.table.sort.icon.color, the container in comp.table.sort.container.color; WHILE its sort is ascending or descending it is the part sorted — the label in comp.table.sorted.label.color, the glyph arrow_upward for ascending and arrow_downward for descending in comp.table.sorted.icon.color, the container in comp.table.sorted.container.color. THE table SHALL NOT reorder, compare, filter or hide a row, SHALL NOT change a column's sort itself, and SHALL draw its rows in the order it is given whatever any sort says. WHEN a column's sort changes THE table SHALL draw the other glyph and colors at once, with no motion but that of a state change (TBL-35). IF more than one column's sort is not none THE table SHALL draw each as it is given and in debug builds report it: the rows have one order. A sort on a column whose sortable is false SHALL be ignored.
TBL-34 WHEN the user activates a sort button by pointer, touch, Enter or Space THE table SHALL emit sort exactly once, carrying the column's value, its index among the columns of data and the direction asked for — ascending WHILE the column's sort is none or descending, descending WHILE it is ascending — and SHALL change nothing it draws but the answer to the press: the mark, the colors and the rows stay as they are until the product sets sort and gives the rows in their new order. The direction is what the person asked; the product MAY set another, or none. THE table SHALL keep keyboard focus on the sort button through the press and through the change of rows that follows it. WHERE the product gave the control a destination in place of an action (on the web an anchor in the header cell, whose address is the same table in the other order) THE table SHALL emit sort once and then let the platform follow the destination unless the product cancelled the event, and Space SHALL NOT activate it, as it activates no link.
TBL-35 WHILE a sort button is hovered, keyboard-focused or pressed THE table SHALL render its container, its label, its mark and its opacity from the component tokens of its part (sort or sorted) in the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state. A change between states SHALL take comp.table.motion.state-duration with comp.table.motion.state-easing and SHALL be immediate where motion is reduced. The state layer SHALL cover the header cell and nothing beside it, and WHILE the header row or the column is kept in view (TBL-19, TBL-20) SHALL be drawn over comp.table.sticky.container.color, so that nothing shows through it.
TBL-36 WHEN a sort button receives keyboard focus THE table SHALL show the system's focus indicator inside the edge of its header cell without animation — sys.focus.outer-width wide in the color role focus-ring, set in from the edge by sys.focus.offset so that neither the next cell, the box's edge nor a header kept in view clips it, with the inner stroke of sys.focus.inner-width where the system has one; focus gained by pointer or touch SHALL NOT show it. WHEN keyboard focus moves to a sort button, a checkbox or a radio button that is under a header row, a first column or a status line kept in view, or outside the box, THE table SHALL scroll its box so that the whole control is in view beside them.
TBL-37 WHEN a sort button is pressed by pointer, touch, Enter or Space THE table SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner.
TBL-38 WHILE a sort button is pressed THE table SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; label, mark and opacity still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.table.motion.feedback-duration with comp.table.motion.state-easing, fades over comp.table.motion.state-duration with comp.table.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
TBL-39 THE table SHALL show the keyboard focus of a sort button as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README. A sort button's container has square corners, the header cell's, so sys.shape.corner-smoothing has nothing to ease on it.
TBL-40 THE table SHALL make a sort button's hit area its whole header cell, with no gap to the hit area of the header cell beside it. WHERE input is touch, or the platform cannot tell the input type, a header row that holds a sort button SHALL be at least sys.target.touch high, drawn so, at both densities, because a header cell cannot extend a hit area over the row under it; for a fine pointer it SHALL be at least sys.target.pointer high WHILE density is regular, and WHILE density is compact as high as its density gives. A header cell narrower than the target SHALL extend its hit area along the row no further than halfway into the paddings between it and the cell beside it.
TBL-41 THE table SHALL expose a sortable column's header cell as the column header it is (TBL-24) with the order its sort names — ascending, descending, or none for a column that can be sorted and is not — and the sort button inside it as a button named by the column's header; the sort mark SHALL be hidden from assistive technology. On the web these are aria-sort on the header cell and the product's own button in it. WHERE a platform has no sort state THE table SHALL say the order after the button's name with sortedAscendingLabel or sortedDescendingLabel, which come from the instance or from the product's words under the keys of the same names and from nowhere else; IF such a platform is given neither, THE table SHALL expose the button by its name alone and in debug builds report the missing word once. THE table SHALL NOT announce an order itself when it changes: the header cell's state says it, and a product that wants it spoken writes it in status (TBL-52).
TBL-42 WHILE selection is multiple or single THE table SHALL draw a selection column before its first column: in the header row a cell holding the checkbox for every row WHILE selection is multiple (TBL-45) and nothing WHILE it is single (TBL-60), in each body row that has a value a cell holding that row's control — a checkbox WHILE selection is multiple, a radio button WHILE it is single — and an empty cell in a body row without a value, in a placeholder row and in a footer row. Each checkbox is the Checkbox component and each radio button the Radio button component, as their own specs draw them, with no label drawn, placed on the cell's first line (TBL-15) with the edge of its box or ring, not of its round container, at the cell's padding, so that the boxes and rings stand on the line the text of the other cells starts at: the selection cell's paddings are a cell's paddings less half of what the control's container exceeds its box by — comp.checkbox.container.size over comp.checkbox.box.size, comp.radio-button.container.size over comp.radio-button.ring.size — (never less than 0), the round container reaching into them, and the column is as wide as the control's container and those paddings. The selection column is not a column of data: it has no header text, the first column of rowHeaders and of stickyFirstColumn is the one after it, and a sort event's index does not count it. WHILE stickyFirstColumn is true the selection column SHALL stay in view with the first column (TBL-20), and the two together are what is released when they would take more than half of the box. WHILE selection is none THE table SHALL draw no such column and no checkbox or radio button, SHALL ignore every row's selected, and SHALL emit no selectionChange.
TBL-43 WHILE selection is multiple or single and a body row's selected is true THE table SHALL draw that row as chosen: comp.table.row.selected.container.color across its whole width, paddings and the selection cell included, in place of the stripe of TBL-11 where the row would have one; the text of its cells and of its row header in comp.table.row.selected.text.color; and its checkbox or radio button checked. What a cell holds that is not plain text SHALL keep its own colors (TBL-15). In a column kept in view the fill SHALL lie over comp.table.sticky.container.color. The lines and the gap between rows (TBL-8, TBL-10) SHALL be drawn as without it. The fill SHALL change at once, without a motion of the table's own. A footer row and a placeholder row are never chosen.
TBL-44 WHEN the user changes a row's checkbox or radio button — by a press, by Space while it has focus, or, for a radio button, by an arrow key (TBL-61) — THE table SHALL set that row's selected to the control's new state, WHILE selection is single let the row that was chosen go (TBL-60), and emit selectionChange exactly once, carrying the values of every chosen row after the change in row order. A press on the row outside its selection cell presses its control where TBL-58 says so, and this sentence holds for that change as for any other. WHEN the product sets a row's selected THE table SHALL draw it and SHALL NOT emit. THE table SHALL keep which rows are chosen by their values: WHEN rows are given again in another order (TBL-33) each row SHALL be drawn chosen or not by its own selected, and the table SHALL carry no selection over to a row it is not given.
TBL-45 WHILE selection is multiple THE table SHALL keep the checkbox for every row in step with the body rows it holds that have a value: checked WHILE each of them is chosen, in its mixed (indeterminate) state WHILE some are and some are not, unchecked WHILE none is, and disabled WHILE there is no such row. WHEN the user activates it while it is unchecked or mixed THE table SHALL choose every such row, and while it is checked SHALL clear every one, and SHALL emit selectionChange exactly once with the values after the change. "Every row" is every row the table has been given: it SHALL NOT claim to choose records the product has not given it (the rows of other pages), and a product that offers that says so with a control of its own. WHILE selection is single there is no such checkbox (TBL-60).
TBL-46 THE table SHALL name each row's checkbox or radio button by that row's selectLabel; WHERE selectLabel is empty and rowHeaders is true the control SHALL be named by the text of the row's header cell; IF it has neither THE table SHALL expose the control without a name and in debug builds report the row once per table. WHILE selection is multiple THE table SHALL name the checkbox for every row by selectAllLabel, from the instance or from the product's words under the key selectAllLabel and from nowhere else; IF neither gives it THE table SHALL still draw the checkbox, expose it without a name and in debug builds report the missing word. A table whose selection is multiple or single SHALL still be exposed as a table (TBL-24), its rows as rows and its selection cells as cells: which rows are chosen is said by the checked state of their checkboxes or radio buttons, the header checkbox says checked, mixed or not checked, and the table SHALL add no selected state to a row and no role of a grid.
TBL-47 THE table SHALL make each sort button and each checkbox its own stop in the focus order, and the radio buttons of a table whose selection is single one stop together (TBL-61), in reading order — the scroll region first while the table scrolls (TBL-22), then the header row's controls from the start, then each row's checkbox, or the one radio button that is the stop, followed by whatever in that row's cells takes focus — and SHALL handle no key that moves focus from cell to cell: the arrow keys scroll the region or do what the focused control does with them, which for a radio button is TBL-61. A checkbox's or radio button's hit area SHALL be its whole selection cell, in width and in height; for a pointer the rest of its row answers too (TBL-58). WHERE input is touch, or the platform cannot tell the input type, a row that holds a checkbox or a radio button SHALL be at least sys.target.touch high, drawn so, at both densities, and its selection cell at least sys.target.touch wide; for a fine pointer the row SHALL be at least sys.target.pointer high WHILE density is regular, and WHILE density is compact as high as its density gives.
TBL-48 WHILE the table has no body row, loading is false and the empty slot has content THE table SHALL draw that content in the place of the body rows: under the header row and its line, before any footer row, as wide as the table's box and staying in view while the columns scroll sideways, with comp.table.empty.padding-block above and below it and a cell's inline padding at its sides (TBL-4), with no line, no stripe and no fill of the table's through it. THE table SHALL expose it as one row holding one cell that spans every column, SHALL add no word, glyph or role of its own to it, and SHALL NOT announce it (TBL-25): where a search or a filter emptied the table, the product's Empty state is the live one. WHILE the table has no body row and the empty slot has no content THE table SHALL draw its caption, its header row and its footer rows and nothing between them. WHILE the table has a body row THE table SHALL NOT draw the empty slot's content.
TBL-49 WHILE loading is true THE table SHALL be exposed as busy; SHALL draw the rows it is given as they are — the rows it had, or placeholder rows (TBL-50) — and SHALL NOT draw the empty slot's content, even with no body row; SHALL keep its header row, its sort buttons, its checkboxes, its radio buttons and its status line working as without it; SHALL change no color, no opacity and no size of its own because of it; and SHALL announce nothing itself when loading begins or ends: the product says it in status (TBL-52). THE table SHALL fetch nothing, wait for nothing and time nothing: loading is true for as long as the product says so.
TBL-50 WHERE a row's placeholder is true THE table SHALL draw it as a body row — counted for stripes, parted from its neighbours as TBL-9 says, with a cell's paddings — at least as tall as a body row holding one line of text at the table's density, however little its cells hold; SHALL place in each cell what the product gives it there (a Skeleton, by TBL-15) or nothing; SHALL draw no checkbox or radio button in its selection cell and count it neither as a body row for the header checkbox (TBL-45) nor as a row for the empty slot (TBL-48); and SHALL hide the row from assistive technology, so that a person hears that the table is busy and not a row of nothing. IF a row's placeholder is true while loading is false THE table SHALL draw it all the same and in debug builds report it.
TBL-51 WHERE status is not empty or the pagination slot has content THE table SHALL draw a status line under its last row: the status text at the start edge in comp.table.status.text.typescale and comp.table.status.text.color, the pagination slot's content at the end edge, both centered on the line's height, with comp.table.status.padding-block above and below, a cell's inline padding at its sides (TBL-4) and at least comp.table.status.gap between the two; WHERE the two do not fit on one line THE table SHALL put the status text above the pagination, comp.table.status.gap apart, each at the start edge, and the text SHALL wrap and never be cut. The status line SHALL be as wide as the table's box and SHALL stay where it is while the columns scroll sideways (TBL-17). WHILE separation is divider THE table SHALL draw one line of comp.table.divider.thickness in comp.table.divider.color above it. WHILE the table scrolls its rows in its own box (TBL-18) the status line SHALL stay at the bottom of that box while the rows pass under it, filled with comp.table.sticky.container.color, with that line above it whatever the separation is while a row is under it. WHERE status is empty and the slot has no content THE table SHALL draw no status line and leave no room for one.
TBL-52 THE table SHALL expose the status text as a status that assistive technology is told of politely WHEN it changes, without moving focus, and SHALL tell it nothing when the text is first drawn with the table. THE table SHALL write no word into it and SHALL count nothing for it: how many rows there are, which of them are shown and how many are chosen are the product's to say, in its own language. THE table SHALL place the content of the pagination slot and SHALL NOT style it, give it keys or listen to it: the table shows the rows it is given, emits nothing about pages, and holds no page number and no page size.
TBL-53 WHERE the layout direction is right-to-left THE table SHALL draw the selection column at the right edge, before the first column; SHALL place a sort mark after its label on the left, and before it — on the right — in a column whose align is end; SHALL draw the status text at the right edge of the status line and the pagination at the left; and SHALL keep the selection column and a first column that stay in view at the right (TBL-27). The glyphs swap_vert, arrow_upward and arrow_downward show an order and not a direction of reading, and SHALL NOT be mirrored. The status text SHALL be laid out as text of its own direction (TBL-28).
TBL-54 WHERE the platform draws with a person's own forced colors THE table SHALL draw a sort button's label and mark in the platform's button-text color and its focus indicator in the platform's highlight color, SHALL keep the sorted column told from the others by its glyph alone, and, because the mode removes the fill of a chosen row, SHALL rely on the row's control — its checkbox, which the Checkbox draws checked in that mode, or its radio button, which the Radio button draws selected there — to say that the row is chosen; it SHALL draw the line above a status line kept in view in the platform's text color (TBL-30).
TBL-55 WHILE the text size is enlarged THE table SHALL grow a sort button's label and its mark's box with it, wrap the label inside its column where the column's wrap allows it and keep the mark on the label's last line, grow the status text and wrap it, keep the selection column as wide as its checkbox or radio button and paddings, and lay the status line out in two lines where one no longer fits (TBL-51); nothing of these SHALL be cut, and TBL-29 holds for a table with every one of them at a width of 320 points.
TBL-56 WHILE the system's icon set cannot yet draw a sort mark (its font is still loading) THE table SHALL draw nothing in the mark's box, never the glyph's name as text, SHALL keep the box, and SHALL draw the glyph once the set can; the sort button SHALL work all the same.
TBL-57 WHERE the platform draws the product's own elements and script does not run (the web) THE table SHALL still draw, from the product's markup alone, everything of TBL-32, TBL-33, TBL-42, TBL-43, TBL-48, TBL-50 and TBL-51 that is drawing: a sort button and the mark of the order the header cell names, the hover, focus and press of the button, the selection column, a chosen row, the empty content, placeholder rows and the status line; a sort button that is an anchor, and checkboxes and radio buttons that are the platform's own inside the product's form, SHALL work as the platform makes them work — radio buttons of one name exclude each other and share one stop without script (TBL-60, TBL-61). What needs script and is then absent: the events, a press on a row outside its checkbox or radio button and the row's hover and press (TBL-58, TBL-59), which are then not drawn, the header checkbox kept in step and choosing every row (TBL-45), the busy state where the product did not write it, keeping focus through a change of rows, and what TBL-22 already loses. Nothing SHALL be hidden that script would have to reveal.
TBL-58 WHILE selection is multiple or single, WHEN a body row that can be chosen (it has a value and a checkbox or radio button, TBL-42) is pressed and released outside its selection cell with the primary mouse button, a touch or a pen, without the pointer moving further than the platform's touch slop between the two, THE table SHALL press that row's control exactly as a press on the control does: a checkbox's row is chosen or let go, a radio button's row is chosen where it was not and stays chosen where it was (TBL-60), and selectionChange is emitted once where the choice changed (TBL-44). For a pointer the whole row is the control's hit area, the way a whole card answers a press while the controls inside it keep their own. THE table SHALL NOT press the control IF the press lands on or inside something in a cell that answers a press itself — a link, a button, a field, a select, a label, a summary, an editable element, an element with a tab index, an element with a widget role, a custom element that takes focus, or an element to which the product gave the pointer cursor where the row's own is not (the pointer the row shows for itself does not count) — which keeps its own behaviour; IF the release ends a text selection, that is, the selection is not empty and lies in the row once the press is released; IF the pointer moved further than the slop (a drag, a scroll); IF a modifier key (Control, Command, Shift, Alt or Option) is held or a button other than the primary is used; IF the press is the second or a later press of a multiple click; IF the product cancelled the click; or IF the press lands inside a table nested in a cell, whose rows are its own. A press in the selection cell is the control's (TBL-47). The header row, whose checkbox stands for every row, a footer row, a placeholder row and a body row without a value SHALL take no such press. The row SHALL get no role, no name, no focus stop, no selected state and no announcement of its own: the checkbox or radio button stays the one control of the row, which the keyboard and assistive technology reach as TBL-46, TBL-47 and TBL-61 say, and a press on the row is a larger target for the same action. WHERE the platform's row answers no press (on the web, without the element's script) the row SHALL show neither the pointer cursor nor a state, and a press there SHALL choose nothing.
TBL-59 WHILE selection is multiple or single and a fine pointer is over a body row that can be chosen where a press would be its control's — its selection cell, or its cells outside anything that answers a press itself (TBL-58) — THE table SHALL draw that row hovered, as one target — the chosen row of a table whose selection is single too, whose press is its radio button's even though it changes nothing, as the radio button itself answers a hover and a press while it is selected — across its whole width, paddings and the selection cell included: its fill in the hovered container color of the row's condition — selected WHILE the row is chosen, unselected otherwise; the default system mixes comp.table.row.selected.state-layer.color or comp.table.row.unselected.state-layer.color into that condition's resting fill — laid over what the row is filled with at rest (its stripe, the chosen fill, comp.table.sticky.container.color in a column kept in view) and short of the gap of TBL-10; the text of its cells in the hovered text color of its condition; and the pointer cursor. WHILE the pointer is over something in a cell that answers a press itself, that thing SHALL keep its own cursor, hover and press, and the row SHALL be drawn at rest. WHEN such a row is pressed by pointer, touch or pen where TBL-58 or TBL-47 makes the press its control's THE table SHALL draw the row pressed without delay and keep it so for at least sys.motion.duration.press-hold, even when the press ends sooner, answering as sys.behavior.pressed-feedback names: overlay draws the pressed container and text colors of the row's condition across the row; dim applies the table's pressed opacity to the whole row while its fill and text stay with the state under the press; none draws no answer; and ripple, which has no one box to grow in across the cells of a row, answers as overlay. The press SHALL end when the pointer leaves the row, moves further than the slop or is cancelled. The fills SHALL change at once, without a motion of the table's own, as the chosen fill does (TBL-43). The checkbox or radio button SHALL keep its own hover, focus and press; a key that presses it or moves to it SHALL NOT draw the row pressed, and its keyboard focus is shown by the control alone. Under forced colors the row's state fills are gone with the other fills (TBL-54).
TBL-60 WHILE selection is single THE table SHALL let one row at most be chosen: each body row that can be chosen holds a radio button in its selection cell (TBL-42), and the header row's selection cell SHALL hold nothing, because no control stands for every row; IF the product puts a control there THE table SHALL NOT draw it and in debug builds report it. The radio buttons of one table SHALL be one group whose options exclude each other (RAD-3): on the web the radio buttons of one name, the name the product gave them or, for those it gave none, one the table gives them. WHEN the user chooses a row that is not chosen — by a press on its radio button or on the row (TBL-58), by Space, or by an arrow key (TBL-61) — THE table SHALL set that row's selected, let the row that was chosen go, and emit selectionChange exactly once, carrying the value of the chosen row alone. WHEN the user activates the radio button of the chosen row, or presses that row, THE table SHALL keep the row chosen and SHALL NOT emit: a radio button is never cleared by its own activation (RAD-2), and a product that lets a person go back to no row chosen offers a control of its own for that, which clears selected. WHEN the product sets selected THE table SHALL draw it and SHALL NOT emit (TBL-44); IF more than one row says it is chosen THE table SHALL draw chosen the one whose selected the product set last — of several set at once, the first in row order — and let the others go without emitting, and in debug builds report several set at once. IF a body row's selection cell holds a checkbox WHILE selection is single, or a radio button WHILE it is multiple, THE table SHALL treat the row as one that cannot be chosen, SHALL NOT draw that control, and in debug builds report it.
TBL-61 WHILE selection is single THE table SHALL expose each row's radio button as a radio, checked or not, named as TBL-46 says, with its position among the table's radio buttons and their number, and SHALL wrap them in no radio group, give a row no selected state and add no role of a grid: a radio group cannot hold the rows of a table, and the table's name and the column the radio buttons stand in say what they choose. THE radio buttons SHALL be one stop in the focus order — the chosen row's, or, while no row is chosen, the first row's that can be chosen — at the place of that row's control in the reading order of TBL-47, and Tab SHALL leave them rather than move between them (RAD-5). WHILE one of them has keyboard focus, Down Arrow and Right Arrow SHALL move focus to the radio button of the next row that can be chosen and Up Arrow and Left Arrow to the previous one's, wrapping from the last to the first, and choose that row as TBL-60 says, with one selectionChange for each key; in a right-to-left layout Left Arrow and Right Arrow SHALL exchange what they do. THE table SHALL scroll its box so that a radio button that receives keyboard focus is in view beside a header row or a status line kept in view (TBL-36), and WHEN rows are given again focus SHALL stay with the radio button of the row of the same value (TBL-25).

Correspondencia de nombres por plataforma

Paridad no significa API idénticas. Cada plataforma expresa el mismo contrato con su propio estilo.

Especificación Web SwiftUI Jetpack Compose Flutter React Native
Componente tanrei-table TanreiTable TanreiTable TanreiTable Table
caption · Propiedad caption caption caption caption caption
label · Propiedad label accessibilityLabel contentDescription semanticLabel accessibilityLabel
density · Propiedad density density density density density
separation · Propiedad separation separation separation separation separation
striped · Propiedad striped striped striped striped striped
rowHeaders · Propiedad rowHeaders rowHeaders rowHeaders rowHeaders rowHeaders
stickyHeader · Propiedad stickyHeader stickyHeader stickyHeader stickyHeader stickyHeader
stickyFirstColumn · Propiedad stickyFirstColumn stickyFirstColumn stickyFirstColumn stickyFirstColumn stickyFirstColumn
selection · Propiedad selection selection selection selection selection
selectAllLabel · Propiedad select-all-label selectAllLabel selectAllLabel selectAllLabel selectAllLabel
sortedAscendingLabel · Propiedad sorted-ascending-label sortedAscendingLabel sortedAscendingLabel sortedAscendingLabel sortedAscendingLabel
sortedDescendingLabel · Propiedad sorted-descending-label sortedDescendingLabel sortedDescendingLabel sortedDescendingLabel sortedDescendingLabel
loading · Propiedad loading isLoading loading loading loading
status · Propiedad status status status status status
sort · Evento sort onSort onSort onSort onSort
selectionChange · Evento selectionchange selection onSelectionChange onSelectionChanged onSelectionChange
Notas Form: <tanrei-table> is a custom element with **no shadow root**, wrapped around a native <table> that stays in the light DOM — <tanrei-table><table><caption>…</caption><thead>…</thead><tbody>…</tbody><tfoot>…</tfoot></table></tanrei-table>. The native table is never given another display, so its semantics, a crawler's reading, copy and paste as a table and a screen reader's table navigation are the browser's own. The wrapper is the scroll box (display: block; overflow: auto; max-inline-size: 100%; the product limits its height with max-block-size). Everything drawn comes from one stylesheet, @tanrei/web/table.css, whose selectors start at tanrei-table and are wrapped in :where(); it styles the page without script (an element that is not yet defined is still an element), and the element adopts the same sheet into the root it is connected to, so it also works inside another component's shadow root. Props are attributes of the wrapper: density, separation, striped, row-headers, sticky-header, sticky-first-column, label. caption is the native <caption>; the caption attribute is a convenience that writes one. label becomes aria-label on the table. Script adds only what CSS cannot: while the wrapper overflows it sets tabindex="0", role="region" and aria-labelledby (the caption) or aria-label (TBL-22), and removes them when it does not; it sets data-more-start and data-more-end for the lines of TBL-21, writes the positions of the columns whose header names data-align or data-wrap on the wrapper (data-columns-end, -center, -nowrap; one rule per position places the cells and no cell is written to), gives first cells role="rowheader" where row-headers is set on a table whose body cells are <td> (Markdown), and releases a first column wider than half of the box (TBL-20). Without script the table is fully drawn, scrolls in its box, and keeps its sticky parts; it lacks the two edge lines and, in a browser that does not focus scrollers by itself, the focus stop. Sticky parts are position: sticky on the cells (inset-block-start: 0 on thead th, inset-inline-start: 0 on the first cell of each row) with border-collapse: separate and the lines drawn as cell borders, because collapsed borders do not travel with a sticky cell. The caption stays in place sideways with position: sticky; inset-inline-start: 0 and a width of the wrapper (100cqi). font-variant-numeric: tabular-nums on cells (TBL-4); unicode-bidi: plaintext per cell (TBL-28). Markdown: a renderer emits a bare <table>; the product wraps it at build time (a rehype step of a few lines, shown in the README) — Tanrei does not transform content. Inside .tanrei-prose the element is an island by its tag name (PRS-16). A cell that spans (colspan, rowspan) and a second header row keep working as HTML and are drawn with the same cell styles, but no fixture covers them. As built: the wrapper is a grid of one cell, not display: block, so that the two "there is more" lines are as tall as the table; the attributes the element writes are data-more-start, data-more-end, data-more-above, data-first-column-released, data-columns and the three positions above, and data-separation on the table; the region's aria-labelledby or aria-label is copied from the table's own name where the product named it natively. **Sorting, selection, empty, loading and status: proposed form, for the platform's agent to settle.** Everything new is the product's own markup inside the same <table>, drawn by table.css without script (TBL-57). Sorting: the stylesheet makes the control in a th[aria-sort] fill the cell (the cell's paddings move onto the control) and draws the mark as a generated glyph from the attribute; the element listens for the control's activation and emits sort (cancelable where the control is an anchor) with detail: { column, index, direction }; it never writes aria-sort. Selection: selection="multiple" on the wrapper; the header row's first cell is <td data-part="selection"> (a cell and not a header cell: a header without text is announced as an empty one) holding the checkbox for every row, which the element keeps checked, indeterminate or unchecked and disables with no row to choose, and names from select-all-label or the product's words (setWords({ selectAllLabel })); the element listens to change from the row checkboxes, writes data-selected, and emits selectionchange with detail: { values }. Neither checkbox is moved, wrapped or replaced. sorted-ascending-label and sorted-descending-label are accepted and not read: aria-sort is the browser's. Empty: the product writes <tbody><tr data-part="empty"><td colspan="…">…</td></tr></tbody> with its <tanrei-empty-state> inside; the stylesheet keeps the cell's content as wide as the box (100cqi) and in view sideways, as it does the caption, and the element corrects a colspan that is not the number of columns. Loading: the loading attribute; the element writes aria-busy on the table (a server may write it itself). Status: after the <table>, inside the wrapper, <div data-part="status"><p role="status">…</p> <nav class="tanrei-pagination">…</nav></div>; it is sticky at the inline start with the box's width, and at the block end, with the sticky fill; the status attribute is a convenience that writes the paragraph, as caption writes a caption. Coarse pointers get the taller header row and rows by @media (pointer: coarse). Proposed spelling, for the platform's agent to settle. TanreiTable(caption:, columns: [TanreiTableColumn], density:, …) { TanreiTableRow { TanreiTableCell("…"); TanreiTableCell { TanreiTag("Beta", size: .small) } } }. Built on Grid and GridRow (columns sized from content, which is TBL-2) inside a ScrollView whose axes are the ones that overflow; a table of several hundred rows uses a LazyVStack of rows with column widths measured once from the header and a sample of rows. The platform's own Table is not used: it is a data table of its own look, with selection and sorting, and it collapses to its first column in a compact width. Sticky parts are pinned section headers (pinnedViews) for the header row and an overlaid first column whose offset follows the scroll. Accessibility: SwiftUI has no table container for custom views, so TBL-24's fallback applies — each row is an accessibility container in reading order, each cell's label is its column header followed by its content, header cells carry the header trait, and the scroll view is named by the caption. Sorting, selection, empty, loading and status: TanreiTableColumn(header:, value:, isSortable:, sort:), TanreiTable(selection: $values, onSort: { column, index, direction in … }) where selection is a binding to the set of chosen values, and rows take value:, selectLabel:, isPlaceholder:. A sortable header is a Button whose accessibility value is sortedAscendingLabel or sortedDescendingLabel (SwiftUI has no sort trait); the row's checkbox is TanreiCheckbox. The empty slot and the pagination slot are view builders (empty:, pagination:); the status text is posted as an announcement when it changes; isLoading sets nothing SwiftUI has a trait for, so placeholder rows are hidden and the status says it. Single selection (selection single, TBL-60, TBL-61): the row's control is the platform's Tanrei radio button in place of the checkbox, the header row holds none, and the product's selection holds one value at most. Proposed spelling, for the platform's agent to settle. TanreiTable(columns = listOf(TanreiTableColumn("Price", align = End)), caption = …) { row { cell("…"); cell { TanreiTag("Beta", size = Small) } } }. Compose has no table layout: a custom Layout measures each column's least and preferred widths (intrinsics) and shares the width as TBL-2 says, inside horizontalScroll; a long table is a LazyColumn of rows with the widths measured from the header and the rows composed so far, and stickyHeader for the header row. Semantics: the container sets collectionInfo(rowCount, columnCount) and each cell collectionItemInfo(rowIndex, columnIndex), header cells are heading(), and each cell's content description is preceded by its column header, so TalkBack announces position and header. The scroll container is focusable and named by the caption. Sorting, selection, empty, loading and status: TanreiTableColumn(header, value = "price", sortable = true, sort = TanreiTableSort.Ascending), onSort = { column, index, direction -> }, selection = TanreiTableSelection.Multiple, rows as row(value = "a1", selected = …, selectLabel = …) with onSelectionChange = { values -> }; the product holds both states. A sortable header is Modifier.clickable(role = Role.Button) with stateDescription from sortedAscendingLabel or sortedDescendingLabel; the checkboxes are TanreiCheckbox (ToggleableState.Indeterminate for the header's); empty and pagination are composable slots; the status text is a polite liveRegion. Single selection (selection single, TBL-60, TBL-61): the row's control is the platform's Tanrei radio button in place of the checkbox, the header row holds none, and the product's selection holds one value at most. Proposed spelling, for the platform's agent to settle. TanreiTable(caption: …, columns: [TanreiTableColumn(header: "Price", align: TanreiTableAlign.end)], rows: [TanreiTableRow(cells: [...])]). Built on the framework's Table widget with IntrinsicColumnWidth (flex for a column that wraps), inside a horizontal SingleChildScrollView in a Scrollbar; a long table uses a two-dimensional scroll view with pinned rows and columns for the sticky parts. The Material library's DataTable is not used: it brings its own look, sorting and selection, and reads a Material theme. Semantics: the table, row, cell and column-header roles where the framework version has them, and otherwise one Semantics per cell whose label is the column header followed by the content; the scroll view is a focusable region named by the caption. Sorting and selection: TanreiTableColumn(header:, value:, sortable:, sort:), onSort: (column, index, direction) {}, selection: TanreiTableSelection.multiple, TanreiTableRow(value:, selected:, selectLabel:, placeholder:) and onSelectionChanged: (values) {}; the product holds both states. A sortable header is an InkWell in Semantics(button: true, value: sortedAscendingLabel); the checkboxes are TanreiCheckbox with tristate for the header's; empty and pagination are widgets; the status text is Semantics(liveRegion: true); placeholder rows are in ExcludeSemantics. Single selection (selection single, TBL-60, TBL-61): the row's control is the platform's Tanrei radio button in place of the checkbox, the header row holds none, and the product's selection holds one value at most. Proposed spelling, for the platform's agent to settle. <Table caption="…" columns={[{ header: "Price", align: "end" }]}><Table.Row><Table.Cell>…</Table.Cell></Table.Row></Table>. React Native has no table layout: rows are Views in a horizontal ScrollView (inside a vertical one, or a FlatList with stickyHeaderIndices for a long table), and column widths are measured in a first pass with onLayout and applied in a second, so the first frame of a large table is drawn after measuring. A first column that stays in view is a second column of cells drawn over the scroll view and kept in step with its vertical offset. There is no table role on either native platform: TBL-24's fallback applies — a row is one accessible group and each cell's accessibilityLabel is its column header followed by its content; header cells have accessibilityRole="header". Follows the app's direction where an ancestor's is not known. Sorting and selection: columns { header, value, sortable, sort }, onSort={({ column, index, direction }) => …}, selection="multiple", <Table.Row value selected selectLabel placeholder> and onSelectionChange={(values) => …}; the product holds both states. A sortable header is a Pressable with accessibilityRole="button" and accessibilityValue={{ text: sortedAscendingLabel }}; the checkboxes are Checkbox (accessibilityState={{ checked: "mixed" }} for the header's); empty and pagination are elements; the status text has accessibilityLiveRegion="polite" on Android and is posted with AccessibilityInfo.announceForAccessibility on iOS; accessibilityState={{ busy: true }} while loading. Single selection (selection single, TBL-60, TBL-61): the row's control is the platform's Tanrei radio button in place of the checkbox, the header row holds none, and the product's selection holds one value at most.

Tokens

Token Alias de Valor resuelto (claro)
--tanrei-comp-table-caption-gap sys.space.space100 8px
--tanrei-comp-table-caption-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-cell-padding-block-compact — 6px
--tanrei-comp-table-cell-padding-block-regular — 12px
--tanrei-comp-table-cell-padding-inline-compact sys.space.space150 12px
--tanrei-comp-table-cell-padding-inline-regular sys.space.space200 16px
--tanrei-comp-table-cell-text-color sys.color.on-surface #1b1b1b
--tanrei-comp-table-divider-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-table-divider-thickness sys.border.hairline 1px
--tanrei-comp-table-empty-padding-block sys.space.space400 32px
--tanrei-comp-table-focused-opacity — 1
--tanrei-comp-table-header-container-color — #000000 · 0%
--tanrei-comp-table-header-gap sys.space.space50 4px
--tanrei-comp-table-header-icon-size sys.icon.extra-small 16px
--tanrei-comp-table-header-icon-weight sys.icon.mark.weight 500
--tanrei-comp-table-header-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-hovered-opacity — 1
--tanrei-comp-table-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-table-motion-state-duration sys.motion.role.state.large 250ms
--tanrei-comp-table-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-table-pressed-opacity — 1
--tanrei-comp-table-row-gap sys.space.space50 4px
--tanrei-comp-table-row-selected-container-color sys.color.secondary-container #262626 · 16%
--tanrei-comp-table-row-selected-hovered-container-color — #262626 · 22.7%
--tanrei-comp-table-row-selected-hovered-text-color — #262626
--tanrei-comp-table-row-selected-pressed-container-color — #262626 · 24.4%
--tanrei-comp-table-row-selected-pressed-text-color — #262626
--tanrei-comp-table-row-selected-state-layer-color comp.table.row.selected.text.color #262626
--tanrei-comp-table-row-selected-text-color sys.color.on-secondary-container #262626
--tanrei-comp-table-row-unselected-container-color — #000000 · 0%
--tanrei-comp-table-row-unselected-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-table-row-unselected-hovered-text-color — #1b1b1b
--tanrei-comp-table-row-unselected-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-table-row-unselected-pressed-text-color — #1b1b1b
--tanrei-comp-table-row-unselected-state-layer-color comp.table.cell.text.color #1b1b1b
--tanrei-comp-table-row-unselected-text-color comp.table.cell.text.color #1b1b1b
--tanrei-comp-table-separation sys.behavior.separation 0
--tanrei-comp-table-sort-container-color — #000000 · 0%
--tanrei-comp-table-sort-focused-container-color — #4b4b4b · 10%
--tanrei-comp-table-sort-focused-icon-color — #4b4b4b
--tanrei-comp-table-sort-focused-label-color — #4b4b4b
--tanrei-comp-table-sort-focused-state-layer-color — #4b4b4b
--tanrei-comp-table-sort-hovered-container-color — #4b4b4b · 8%
--tanrei-comp-table-sort-hovered-icon-color — #4b4b4b
--tanrei-comp-table-sort-hovered-label-color — #4b4b4b
--tanrei-comp-table-sort-hovered-state-layer-color — #4b4b4b
--tanrei-comp-table-sort-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-sort-label-color comp.table.header.text.color #4b4b4b
--tanrei-comp-table-sort-pressed-container-color — #4b4b4b · 10%
--tanrei-comp-table-sort-pressed-icon-color — #4b4b4b
--tanrei-comp-table-sort-pressed-label-color — #4b4b4b
--tanrei-comp-table-sort-pressed-state-layer-color — #4b4b4b
--tanrei-comp-table-sort-state-layer-color comp.table.sort.label.color #4b4b4b
--tanrei-comp-table-sorted-container-color — #000000 · 0%
--tanrei-comp-table-sorted-focused-container-color — #1b1b1b · 10%
--tanrei-comp-table-sorted-focused-icon-color — #1b1b1b
--tanrei-comp-table-sorted-focused-label-color — #1b1b1b
--tanrei-comp-table-sorted-focused-state-layer-color — #1b1b1b
--tanrei-comp-table-sorted-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-table-sorted-hovered-icon-color — #1b1b1b
--tanrei-comp-table-sorted-hovered-label-color — #1b1b1b
--tanrei-comp-table-sorted-hovered-state-layer-color — #1b1b1b
--tanrei-comp-table-sorted-icon-color sys.color.on-surface #1b1b1b
--tanrei-comp-table-sorted-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-table-sorted-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-table-sorted-pressed-icon-color — #1b1b1b
--tanrei-comp-table-sorted-pressed-label-color — #1b1b1b
--tanrei-comp-table-sorted-pressed-state-layer-color — #1b1b1b
--tanrei-comp-table-sorted-state-layer-color comp.table.sorted.label.color #1b1b1b
--tanrei-comp-table-status-gap sys.space.space200 16px
--tanrei-comp-table-status-padding-block sys.space.space100 8px
--tanrei-comp-table-status-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-table-sticky-container-color sys.color.surface #ffffff
--tanrei-comp-table-stripe-container-color sys.color.surface-container-low #f9f9f9
--tanrei-comp-table-caption-text-typescale sys.typescale.body-small 400 13px/1.5385
--tanrei-comp-table-cell-text-typescale-compact sys.typescale.body-small 400 13px/1.5385
--tanrei-comp-table-cell-text-typescale-regular sys.typescale.body 400 14px/1.5714
--tanrei-comp-table-header-text-typescale-compact sys.typescale.label-small 500 12px/1.3333
--tanrei-comp-table-header-text-typescale-regular sys.typescale.label 500 14px/1.4286
--tanrei-comp-table-status-text-typescale sys.typescale.body-small 400 13px/1.5385

Fixtures

Representaciones canónicas. Todas las plataformas capturan cada fixture con el mismo identificador para poder comparar los resultados.

Generado a partir de specs/table.spec.json, tokens/comp/table.tokens.json, design/tokens/

Pautas

¿Es una tabla?

Usa una tabla para datos que tienen filas y columnas. No la uses para colocar cosas una al lado de otra: la tecnología de apoyo anuncia filas, columnas y encabezados en cada celda, y eso es ruido alrededor de un contenido que no es tabular. Para eso, usa el diseño de página.

Una lista de claves y valores sin una segunda dimensión se parece más a una lista de descripciones que a una tabla.

Densidad

density Una fila con una línea de texto Úsalo para
regular (predeterminado) 46 Una tabla que se lee: pocas filas, celdas que saltan de línea.
compact 32 Una tabla que se recorre con la vista: cientos de filas, identificadores y valores.

Una fila no tiene altura propia. Es tan alta como la más alta de sus celdas.

Cómo distinguir las filas

separation Qué dibuja
auto (predeterminado) Lo que eligió el sistema de diseño: una línea fina en el sistema predeterminado.
divider Una línea fina entre filas, bajo la fila de encabezado y sobre las filas de pie.
gap Espacio vacío y ninguna línea.

striped rellena una de cada dos filas del cuerpo con un color discreto. Úsalo para una tabla ancha con muchas filas parecidas, donde la vista pierde su fila al cruzarla. Se puede combinar con cualquiera de las dos separaciones.

Columnas

  • Las columnas toman su tamaño de su contenido. Una columna nunca es más estrecha que aquello que no puede partirse dentro de ella, y una columna de texto largo ocupa lo que dejan las cortas.
  • El texto salta de línea y nunca se corta. Un identificador no se parte por dentro; ensancha su columna.
  • Alinea una columna de números al final, para que las cifras queden en columna. Deja el texto al inicio. Centra solo una marca o una sola palabra que sea el mismo tipo de respuesta en todas las filas.
  • Da tú una anchura a una columna solo donde no haya más remedio (style="width: …" en su celda de encabezado). Sustituye a la que daría el contenido.

Cuando no cabe

  • La tabla se desplaza dentro de su propia caja, nunca la página. Una línea fina a lo largo de un borde de la caja dice que hay columnas más allá. No necesita un área de desplazamiento alrededor: marca sus propios bordes.
  • sticky-first-column mantiene la primera columna a la vista mientras las demás se desplazan por debajo. Se suelta por sí sola donde ocuparía más de la mitad de la caja, de modo que la columna que se queda nunca llena un teléfono.
  • sticky-header mantiene la fila de encabezado a la vista mientras las filas se desplazan. Funciona donde tu diseño limita la altura de la tabla (max-block-size en el elemento); no sigue a la página.
  • Una tabla que está sobre una superficie que no es la de la página (una tarjeta, un diálogo) define --tanrei-comp-table-sticky-container-color con el color de esa superficie donde mantiene a la vista su fila de encabezado o su primera columna, o donde tiene una línea de estado. Esas partes se rellenan con ese color, para que las filas y las columnas que pasan por debajo de ellas no se transparenten.

Pautas para ordenar

  • Haz que una columna se pueda ordenar donde una persona buscaría una fila por ella: un nombre, una fecha, un importe, una categoría. Una columna de notas no es una de ellas.
  • La marca se dibuja en todas las columnas que se pueden ordenar, también en reposo, para que una pantalla táctil muestre qué encabezados se pueden pulsar.
  • Se ordena por una sola columna a la vez. Un segundo criterio de ordenación es un control tuyo.
  • Di el nuevo orden en la línea de estado donde quieras que se lea en voz alta («Ordenado por precio, de mayor a menor»). La tabla no anuncia nada por sí misma.
  • La fila de encabezado de una tabla con una columna que se puede ordenar tiene al menos 44 de alto con la densidad regular, y 48 para el tacto con las dos densidades.

Pautas para elegir filas

  • Deja que las personas elijan filas donde actúan sobre varios registros a la vez: eliminar, exportar, mover. Para un solo registro, pon la acción en su fila.
  • Elige una sola fila (single) donde la respuesta es un registro: un plan, una dirección, una versión con la que comparar. No lo uses para abrir un registro.
  • Una pulsación en cualquier punto de una fila la elige. Si una fila también abre su registro, pon para eso un enlace en su encabezado de fila, no una pulsación en la fila.
  • Di en la línea de estado cuántas filas están elegidas («Seleccionados: 3 de 132»). La tabla no cuenta nada.
  • Pon encima de la tabla los botones que actúan sobre las filas elegidas. Son tuyos; la tabla te dice qué está elegido.
  • Ofrece «todos los 132» con un control tuyo. La casilla de verificación de la fila de encabezado elige las filas que se le dieron a la tabla, nunca las filas de otras páginas.
  • Cada casilla de verificación es una parada en el orden del foco. Limita una página de filas a un número que una persona pueda recorrer con el tabulador, y pagina el resto. Los botones de opción de una tabla son, juntos, una sola parada.
  • Una fila que contiene una casilla de verificación o un botón de opción tiene al menos 44 de alto con la densidad regular, y 48 para el tacto con las dos densidades. Una fila compacta sigue siendo compacta para un puntero.
  • Un tag y el código en línea son tonales salvo que elijas otra variante, y en el sistema predeterminado el relleno tonal es casi el de una fila elegida, así que sobre una fila elegida su forma se pierde. Su texto se sigue pudiendo leer. Usa el tag con contorno en una tabla cuyas filas se eligen.

Pautas para la carga

  • Usa filas de marcador de posición para una primera carga, más o menos tantas como las filas que esperas.
  • Conserva las filas en un cambio de orden o de página. No sustituyas por filas de marcador de posición ni por un indicador de carga las filas que una persona está leyendo.
  • Di «Cargando» en la línea de estado, y di qué llegó cuando termine («1–20 de 132»).
  • Quita las filas de marcador de posición cuando quites loading. Una que se queda se sigue dibujando, y las compilaciones de depuración la notifican.

Sin filas y la línea de estado

  • Usa el estado vacío para una tabla sin filas, y di por qué no hay ninguna y qué hacer a continuación. Un estado vacío que responde a una búsqueda o a un filtro es live, para que el resultado se diga.
  • Reserva la línea de estado para lo que la tabla muestra ahora: cuántas filas hay y cuáles de ellas se muestran. Los botones que actúan sobre las filas van encima de la tabla.

Recomendado

  • Da un nombre a cada tabla: una leyenda que todo el mundo ve, o label cuando el encabezado que tiene encima ya dice qué es.
  • Escribe la primera columna como encabezados de fila (<th scope="row"> y row-headers) siempre que diga qué es cada fila.
  • Deja vacía una celda cuando no haya nada que decir. Cuando la propia ausencia sea información («no disponible»), escríbela.

Evita

  • No uses una tabla para componer una pantalla.
  • No dependas de las franjas ni del color para transmitir significado. Un estado en una celda es una palabra, o un tag cuya etiqueta lo dice.
  • No pongas un guion en una celda vacía para llenarla.
  • No hagas que una fila se pueda pulsar, y no escribas en un encabezado una marca de ordenación ni un orden propios. Un encabezado se puede pulsar por el marcado de más arriba y por nada más. Una fila se elige con su casilla de verificación o su botón de opción, y las filas que abren algo no forman parte de la tabla.
  • No atenúes ni deshabilites una tabla mientras carga.

Accesibilidad

Rol
table
Etiquetado
The table is named by its caption, or by label where no caption is drawn (TBL-23). Header cells name their columns and, while rowHeaders is true, first cells name their rows; every cell is associated with both (TBL-24). While the table scrolls in its own box, that box is a region with the same name, so that a person who reaches it by keyboard hears what scrolls. A sortable column's button is named by the column's header, and the header cell carries the order (TBL-41). A row's checkbox or radio button is named by selectLabel, or by the row's header; the header row's checkbox by selectAllLabel (TBL-46). The radio buttons of a table are not wrapped in a radio group: each is exposed with its position among them (TBL-61). The status line is the product's sentence, told politely when it changes (TBL-52).
Objetivo mínimo
El área de pulsación cumple el tamaño mínimo de objetivo aunque el tamaño visual sea menor. minimum 32px · pointer 44px · spacing 8px · touch 48px

Teclado

Teclas Acción
Tab Moves focus to the table's box while the table scrolls in it; then to each sortable column's header button, in reading order; then, row by row, to the row's checkbox where rows can be chosen — where one row at most is chosen, once to the chosen row's radio button, or the first row's while none is — and to whatever in its cells takes focus. A table that does not scroll, sorts nothing and selects nothing is not a stop.
Enter / Space On a sortable column's header: asks for the rows in that column's order — ascending, or descending where they already ascend by it. Where the header is a link, Enter alone.
Space On a row's checkbox: chooses the row or lets it go. On the header row's checkbox: chooses every row, or lets every row go where all are chosen. On a row's radio button: chooses the row where it is not chosen; on the chosen row's, nothing.
Arrow keys On a row's radio button: move to the next row's radio button (Down, and Right in a left-to-right layout) or the previous one's, wrapping, and choose that row. While the box has focus: scroll it. Left and right move the view left and right in either layout direction.
Page Up / Page Down, Home / End While the box has focus: scroll it by a page, or to its beginning or end, as the platform scrolls a region.

Notas

  • The table, its cells and every row that cannot be chosen take no input (TBL-16). What does — a sortable column's header, the checkboxes, a row that can be chosen (for a pointer), the scroll box — is a control, a larger target for one, or a region: a header's hit area is its whole cell and a checkbox's its whole selection cell, each at least the touch target high for touch at both densities (TBL-40, TBL-47); the compact density is lower than the pointer target, as a compact tree row is. What is pressable inside a cell (a link, a button) meets the minimum target by its own spec.
  • A table is for data that has rows and columns. Do not use it to place things side by side: assistive technology announces rows, columns and headers for every cell, which is noise around content that is not tabular. Use the layout.
  • Give every table a name (WCAG 1.3.1, 4.1.2): a caption that everyone sees is best; label is for a table whose heading already names it. A table that scrolls and has no name is a focus stop a person cannot tell from any other, so debug builds report it (TBL-23).
  • Header cells are structure, not style (WCAG 1.3.1). Every column has a header cell unless the table has no header row at all; rowHeaders is true whenever the first column says what each row is. A table with two levels of column headers, or cells that span columns or rows, is outside this spec: split it.
  • A scrolling box must be reachable and operable by keyboard (WCAG 2.1.1) and its focus must be visible (2.4.7): TBL-22. It is a stop only while it scrolls, so short tables add nothing to the focus order.
  • Reflow (WCAG 1.4.10) exempts data tables from two-dimensional scrolling, and the table uses that exemption carefully: only its own box scrolls sideways, never the page (TBL-17, TBL-29), and a first column that stays in view is released on a narrow window (TBL-20).
  • Meaning never rests on color: stripes are an aid to the eye and say nothing; a status in a cell is a word, or a tag whose label says it. An empty cell is exposed as empty (TBL-14); where the absence itself is information ("not available", "none"), write it in the cell.
  • Contrast in the default system: cell text is on-surface and header text on-surface-variant, each held to 4.5:1 on every surface and container step by the token invariants, the stripe included. The lines are decorative: rows, headers and the region are exposed without them.
  • Alignment end for numbers is for sighted comparison only; it changes nothing that is exposed. Text is never justified and never centred except in a column the product sets to center.
  • A table with checkboxes stays a table (TBL-46). The grid pattern — one stop for the whole table, arrow keys from cell to cell — is for a sheet whose cells are edited or acted on; it takes over the keys a screen reader uses to read a table, it would replace the roles of the product's own table on the web, and it cannot work without script. The price of staying a table is one Tab stop for each row's checkbox, which pagination keeps to a page of rows.
  • Where a person chooses one record — the plan to change to, the address to ship to, the build to compare against — selection is single, and the selection column holds a radio button, as the single selection of Carbon's data table and of Ant Design's table does: a radio says that the choices exclude each other, and that one is answered once it is (TBL-60). A radio is never cleared by its own activation (HTML, WAI-ARIA APG radio group), so the table offers no way back to none: a product that needs it gives a control of its own. The radio buttons share one name and so behave as one radio group — one Tab stop, the arrow keys move the choice (TBL-61) — which also keeps a page of rows to one stop where checkboxes would be one each. They are not wrapped in an element of the radio group role, which cannot hold the rows of a table; each radio is heard with its row's name, its state and its position ("Select Kettle, radio button, checked, 3 of 4"), inside the table's name. The grid alternative, rows that carry aria-selected (React Aria's table, single selection mode), is declined for the reasons above.
  • A press anywhere on a row that can be chosen presses its checkbox or radio button (TBL-58), as a press on a card or on a list item that can be chosen does: a larger target for a pointer (WCAG 2.5.8), not a second control. The row gets no role, no tab index and no selected state — ARIA allows aria-selected on a row only inside a grid or a treegrid — so that the keyboard and assistive technology meet the one control the row has, its named checkbox or radio button, and lose nothing. What a cell holds that answers a press (a link, a button, a field) keeps it, and a drag that selects text chooses nothing, so the text of a table can still be selected and copied.
  • Sorting is asked for and shown, never done, by the table (TBL-33, TBL-34): the header cell says the order (WCAG 1.3.1, 4.1.2) and the mark repeats it to the eye without relying on color (1.4.1). One column is sorted at a time.
  • A chosen row is said by its checkbox or radio button, checked; the fill is the same fact for the eye and is not relied on (WCAG 1.4.1), which is also what is left in forced colors (TBL-54). In the default system the text of a chosen row is on-secondary-container on a layer of that color over the surface, the pair a selected list item and a selected tree row are drawn in, held to 4.5:1 by the token invariants, and the fill stands 1.1:1 at least from a hovered row; the contrast of what a cell holds beside plain text on that fill is its own component's.
  • Status messages (WCAG 4.1.3): the table announces nothing itself (TBL-25) and gives the product one polite place to say what changed — rows found, rows chosen, loading and loaded (TBL-52). An Empty state in the empty slot that answers a search or a filter is live by its own spec.
  • While loading, the table is busy and placeholder rows are hidden (TBL-49, TBL-50), so that a person does not walk through rows of nothing. Do not take the rows a person was reading away for a spinner where they can stay.
  • Focus is never lost to a change of rows (TBL-25) and a focused control is never left under a part kept in view (TBL-36; WCAG 2.4.11).

Generado a partir de specs/table.spec.json, tokens/sys/measure.tokens.json

De qué se encarga el componente

  • La tabla nativa sigue siendo una tabla: las filas, los encabezados de columna, los encabezados de fila y las celdas son los del propio navegador, con franjas o sin ellas, con cualquiera de las dos densidades, mientras se desplaza y mientras una columna o el encabezado se mantienen a la vista.
  • La leyenda da nombre a la tabla. Sin ella, label se convierte en el aria-label de la tabla y no se dibuja. Con ambos, el nombre es la leyenda.
  • Mientras la tabla se desplaza dentro de su propia caja, esa caja es una parada en el orden del foco, anterior a cualquier cosa que haya en sus celdas, y una región con el nombre de la tabla. Las teclas de flecha, Re Pág, Av Pág, Inicio y Fin la desplazan. Una tabla que no se desplaza no añade ninguna parada.
  • El anillo de foco se dibuja por dentro del borde de la caja, así que nada lo recorta.
  • Cuando las primeras celdas de una tabla son <td> sin más (Markdown renderizado) y row-headers está definido, el elemento les da el rol de encabezado de fila.
  • En un diseño de derecha a izquierda, las columnas van desde la derecha, el desplazamiento empieza por la derecha, una primera columna que se queda está a la derecha, y el texto de cada celda conserva su propia dirección: un número nunca se invierte.
  • Con el texto al 200%, las columnas vuelven a tomar su tamaño y la tabla se desplaza dentro de su propia caja donde ya no caben.
  • Con colores forzados, todas las líneas tienen el color del texto, y una tabla con franjas dibuja las líneas de sus filas sea cual sea su separación, porque ese modo quita los rellenos.
  • La celda de encabezado de una columna que se puede ordenar es un encabezado de columna que lleva el orden (aria-sort), y tu botón dentro de ella recibe su nombre del texto del encabezado. La marca queda oculta para la tecnología de apoyo.
  • Un botón de ordenar es una sola parada en el orden del foco. Enter y Espacio lo pulsan; donde es un enlace, solo Enter. El foco sigue en él después de la pulsación. Donde sustituyes la fila de encabezado junto con las filas, el foco va al nuevo botón de la misma columna.
  • El indicador de foco de un botón de ordenar se dibuja dentro de su celda de encabezado, y un botón que recibe el foco con el teclado se saca de debajo de una primera columna o de una línea de estado que se mantienen a la vista.
  • La fila del estado vacío es una sola fila con una sola celda. La tabla no le añade ningún rol ni ningún texto.
  • El párrafo de la línea de estado es un estado: un cambio de su texto se dice de forma cortés, sin mover el foco, y no se dice nada cuando se dibuja por primera vez.
  • Con colores forzados, la etiqueta y la marca de un botón de ordenar toman el color de texto del botón, y la columna ordenada se distingue solo por su flecha.
  • Sin script, los botones de ordenar, sus marcas, la fila del estado vacío y la línea de estado se dibujan igualmente, y un enlace de ordenación funciona. El evento sort necesita script.
  • Una tabla cuyas filas se pueden elegir sigue siendo una tabla. No recibe el rol de cuadrícula, una fila no recibe el estado de seleccionada y ninguna tecla mueve el foco de celda en celda. El estado marcado de la casilla de verificación o del botón de opción de una fila dice que la fila está elegida.
  • La casilla de verificación de la fila de encabezado dice marcada, sin marcar o mixta mientras es indeterminada, y está deshabilitada mientras no hay ninguna fila que elegir.
  • Cada casilla de verificación es una parada propia en el orden del foco: primero la casilla y los botones de ordenar de la fila de encabezado, y después la casilla de cada fila. Espacio la marca o la desmarca.
  • La casilla de verificación o el botón de opción de una fila que no tiene nombre propio recibe su nombre de la celda de encabezado de la fila.
  • Con single los botones de opción son una sola parada: el tabulador llega al de la fila elegida (al de la primera fila mientras no hay ninguna elegida) y la siguiente pulsación sale de la tabla. Las teclas de flecha pasan al botón de opción de la fila siguiente o anterior y eligen esa fila, y al llegar al final vuelven al otro extremo. Cada botón de opción se oye con el nombre de su fila, su estado y su posición entre ellos, por ejemplo «Seleccionar Equipo, botón de opción, marcado, 2 de 3». No se envuelven en un grupo de botones de opción, que no puede contener las filas de una tabla.
  • Una pulsación en cualquier punto de una celda de selección pulsa su casilla de verificación. Cuando sustituyes las filas o cambias su orden mientras una casilla tiene el foco, el foco va a la casilla de la fila que tiene el mismo data-value.
  • Para un puntero, una fila que se puede elegir es un objetivo más grande para su casilla de verificación. La fila no recibe rol, parada de foco ni estado seleccionado, así que el teclado y un lector de pantalla encuentran la casilla, con su nombre, como antes, y el texto de la fila se sigue pudiendo seleccionar y copiar.
  • Con colores forzados, el relleno de una fila elegida desaparece, y la casilla de verificación o el botón de opción sigue diciendo que la fila está elegida.
  • Mientras loading está definido, la tabla se expone como ocupada (aria-busy). Las filas de marcador de posición quedan ocultas para la tecnología de apoyo, así que una persona oye que la tabla está ocupada y no filas de nada. La tabla no anuncia nada por sí misma cuando la carga empieza ni cuando termina.
  • Sin script, la columna de selección, una fila elegida (data-selected, o una casilla de verificación nativa que está marcada) y las filas de marcador de posición se dibujan igualmente, y las casillas de verificación nativas funcionan en tu formulario. Mantener al día la casilla de la fila de encabezado y el evento selectionchange necesitan script. Sin script, una fila no muestra el paso del cursor ni el cursor de puntero, y una pulsación junto a su casilla no cambia nada.
  • Las compilaciones de depuración notifican una tabla sin nombre, una densidad, una separación o una selección desconocidas, una fila de pie situada antes del cuerpo, una fila con más o con menos celdas que columnas, una columna que se puede ordenar sin encabezado, dos columnas ordenadas a la vez, la casilla de verificación de una fila sin nombre, varias filas marcadas como elegidas a la vez, un control del tipo equivocado con single y una fila de marcador de posición en una tabla que no está en carga.

Qué debes hacer tú

  • Da nombre a la tabla. Una tabla que se desplaza y no tiene nombre es una parada de foco que una persona no puede distinguir de cualquier otra.
  • Da a cada columna una celda de encabezado, y marca los encabezados de fila como encabezados de fila. Los encabezados son estructura, no estilo.
  • Mantén tan sencilla como puedas una tabla que necesite dos niveles de encabezados de columna o celdas que abarquen varias. Funcionan como en HTML, pero ninguna parte de este componente está diseñada en torno a ellas; dividir la tabla suele ser más claro.
  • Lo que se puede pulsar dentro de una celda (un enlace, un botón) conserva su propio nombre, su foco y su tamaño de objetivo. Dale espacio.
  • Después de ordenar las filas, escribe el nuevo aria-sort en las celdas de encabezado: ascending o descending en una, none en las demás que se pueden ordenar. No dejes el atributo en un encabezado que no tiene botón.
  • Mantén el párrafo de la línea de estado en la página mientras no tenga texto, para que se diga el siguiente texto que le des.
  • Da nombre a la casilla de verificación de la fila de encabezado con select-all-label, y a la casilla de cada fila con su label donde la tabla no tiene encabezados de fila.
  • Di en la línea de estado cuándo empieza la carga y cuándo termina. Es el único lugar donde el cambio se dice.

Los textos

Tanrei no incluye textos propios. La tabla te pide uno, el nombre de la casilla de verificación de la fila de encabezado: select-all-label en el elemento, o selectAllLabel con setWords(). Los otros dos los escribes en tu propio marcado.

Texto Dónde lo das
El nombre de la casilla de verificación de la fila de encabezado («Seleccionar todos los productos») select-all-label en el elemento, o una sola vez para todas las tablas con setWords({ selectAllLabel: 'Seleccionar todas las filas' }).
El nombre de la casilla de verificación o del botón de opción de una fila («Seleccionar Molinillo manual») label en el <tanrei-checkbox> o el <tanrei-radio-button> de la fila, o aria-label en uno nativo. Con encabezados de fila puedes omitirlo: el encabezado de la fila da nombre a la casilla.
La frase de la línea de estado («Seleccionados: 2 de 4», «Cargando») El párrafo de la línea de estado, o status en el elemento donde no escribes ningún párrafo.

Sin un nombre para la casilla de verificación de la fila de encabezado, la casilla se sigue dibujando y funciona, no tiene nombre accesible, y una compilación de depuración notifica el texto que falta.