Table
Listagem
| Número | Cliente | Status | Valor | |
|---|---|---|---|---|
| 4812 | Prefeitura de João Pessoa | Paga | R$ 12,4K | |
| Selecionada | 4813 | Clínica São Lucas | Aberta | R$ 3,3K |
| 4814 | Transportes Cabo Branco | Vencida | R$ 8,8K |
Com linha de totais
| Número | Cliente | Valor |
|---|---|---|
| 4812 | Prefeitura de João Pessoa | R$ 12,4K |
| 4813 | Clínica São Lucas | R$ 3,3K |
| 4814 | Transportes Cabo Branco | R$ 8,8K |
| Total | R$ 24,5K | |
Com legenda
| Identificador | Meio | Valor |
|---|---|---|
| PIX-9021 | Pix | R$ 4,2K |
| BOL-4477 | Boleto | R$ 1,9K |
| CTC-1180 | Cartão de crédito | R$ 990 |
import { Table } from '@rivocode/ui'Quando usar
Tabela semântica, com <table> de verdade.
Compõe com TableCaption, TableHeader, TableBody, TableFooter,
TableRow, TableHead e TableCell.
selected na linha desenha uma barra de acento na lateral e abre a primeira
célula com um marcador de texto que só o leitor de tela ouve: "Selecionada",
trocável por labels.selected. Cor sozinha não é estado.
Ela não marca no aria-selected. Esse atributo só vale dentro de grid ou
treegrid; num <table> simples o navegador o descarta, e o estado nunca
chega ao leitor. Virar grid custaria caro: grid exige navegação por setas
entre as células, que esta peça não implementa. O marcador textual entrega o
estado sem prometer um teclado que não existe.
A moldura rola de lado sozinha, então tabela larga não empurra a página.
O nome da tabela entra pelo TableCaption, e não por um <h3> acima dela: o
título vizinho não nomeia elemento nenhum, e o leitor de tela anuncia só
"tabela, 5 colunas".
A linha de totais entra pelo TableFooter, e não por uma <div> embaixo da
tabela: dentro do <tfoot> a célula divide a largura com a coluna, e o total
fica debaixo do valor que ele soma.
Quando não usar
Para listagem que vem de uma consulta, use DataTable. Ela trata os três
estados que toda consulta tem e quase nenhuma tabela escrita à mão trata
(carregando, erro e vazio) e traz ordenação, busca, paginação e seleção sem
nada disso virar estado da sua tela.
Este aqui fica para a tabela que você desenha: o quadro de valores de um
recibo, a comparação de planos, a linha de totais montada à mão. Quando as
linhas são um map sobre o que a API devolveu, é a outra.
No React Native
Não porta, por decisão - não há tabela no celular; a consulta vira DataList. Não é fila: não vai existir. A tabela de paridade diz o porquê de cada uma.
API
Sem prop própria: repassa ao elemento de baixo o que você mandar.
Partes
Table se monta com estas peças. Todas vivem nesta página, porque separar cada uma num endereço obrigaria a abrir seis abas para montar uma tela.
TableBody
/table-body.mdO <tbody>: as linhas de dado.
Não desenha nada por conta própria. O visual da linha mora no TableRow.
Sem prop própria: repassa ao elemento de baixo o que você mandar.
TableCaption
/table-caption.mdA legenda da tabela, num <caption> de verdade. É o nome que o leitor de tela
anuncia antes de entrar nas linhas.
Ela vai dentro do Table, e como primeiro filho:
<Table>
<TableCaption>Notas emitidas em junho de 2025</TableCaption>
<TableHeader>…</TableHeader>
<TableBody>…</TableBody>
<TableFooter>…</TableFooter>
</Table>
O instinto é escrever esse título numa <p> ou num <h3> logo acima da
tabela. Não quebra nada, e custa o nome inteiro: o anúncio vira "tabela, 5
colunas, 12 linhas" e mais nada, porque texto vizinho não nomeia elemento
nenhum. Numa tela com duas tabelas (a das notas e a dos pagamentos), quem
navega por lista de tabelas ouve as duas com o mesmo nome, que é nome nenhum.
E não há meio-termo: <caption> não tem outro pai legal além de <table>.
Solto ao lado do Table, onde o título parece caber melhor, o React derruba a
tela:
In HTML, <caption> cannot be a child of <div>. This will cause a hydration error.
Ou é filho da <table>, ou não é legenda.
Quando a tabela já tem um título na página, a legenda continua valendo a pena como nome, só que sem ocupar pixel nenhum:
<TableCaption className="sr-only">Notas emitidas em junho de 2025</TableCaption>
É o que o DataTable faz com o caption dele.
A legenda sai em cima. Para mandá-la para baixo da tabela, caption-bottom na
classe. O caption-top da peça sai do caminho sozinho.
Quando não usar
Numa listagem que vem de uma consulta, não monte o <caption> à mão: o
DataTable recebe a legenda pela prop caption, e já a escreve dentro da
<table> certa (inclusive na variante com altura, onde a tabela é outra).
Para o título visível que encabeça a seção inteira, com ação do lado, é o
PageHeader, e não a legenda: TableCaption nomeia a tabela, não a tela.
Sem prop própria: repassa ao elemento de baixo o que você mandar.
TableCell
/table-cell.mdUma célula de dado.
Respiro e altura vêm da densidade, então a mesma tabela encolhe junto com o
resto em density="compact". Para número, alinhe à direita e use fonte mono
pela className.
Sem prop própria: repassa ao elemento de baixo o que você mandar.
TableFooter
/table-footer.mdO rodapé da tabela, num <tfoot> de verdade. É onde mora a linha de totais.
Toda listagem financeira daqui termina em "Total: R$ 248,3K", e até agora essa
linha era uma <div> embaixo da tabela. Uma <div> não participa do algoritmo
de layout de tabela: ela não conhece a largura de nenhuma coluna, então o total
nunca fica debaixo do valor que ele soma. E, numa tabela com moldura própria,
ela rola embora junto com o conteúdo.
Dentro da tabela as duas coisas se resolvem sozinhas: a célula divide a largura
com a coluna, e o rodapé pode grudar embaixo pelo mesmo mecanismo com que o
TableHeader gruda em cima.
<Table>
<TableHeader>…</TableHeader>
<TableBody>…</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={2}>Total</TableCell>
<TableCell className="text-right font-mono">{currencyShort(total)}</TableCell>
</TableRow>
</TableFooter>
</Table>
O peso é proposital: o rodapé é resumo, e resumo não pode se ler como mais uma linha de dado.
O dinheiro sai abreviado, como no resto da casa: currencyShort, e não o
valor por extenso. O currency fica para onde o centavo é o assunto: o valor
que a pessoa confirma antes de emitir, e o comprovante depois.
Para grudar o rodapé numa tabela que rola por dentro, o sticky vai na classe,
com o degrau de empilhamento da família:
<TableFooter className="sticky bottom-0 z-[var(--rc-z-sticky)] bg-surface">
O fundo não é opcional: sem ele a linha que passa por baixo aparece através do rodapé.
Quando não usar
Numa listagem que vem de uma consulta, não monte o <tfoot> à mão: o
DataTable produz a linha sozinho, a partir do total de cada coluna, e ali
ele já sabe quais linhas somar, qual coluna esconder no celular e quando grudar.
Sem prop própria: repassa ao elemento de baixo o que você mandar.
TableHead
/table-head.mdUma célula de cabeçalho.
Sai como <th>, em caixa alta e menor, com a altura vinda da densidade. Não
quebra linha: cabeçalho quebrado desalinha a coluna toda.
Sem prop própria: repassa ao elemento de baixo o que você mandar.
TableHeader
/table-header.mdO <thead> da tabela, com a linha que separa do corpo.
Sempre com TableRow e TableHead dentro, cabeçalho montado com <div> some
para quem navega a tabela pelo leitor de tela.
Sem prop própria: repassa ao elemento de baixo o que você mandar.
TableRow
/table-row.mdUma linha da tabela.
selected desenha uma barra de acento na lateral, com fundo tênue. A barra é
que diz "esta linha", e fundo forte mancha a leitura da linha inteira.
Cor sozinha não é estado, então a primeira célula da linha escolhida abre com
um <span> que só o leitor de tela ouve: "Selecionada". É sempre a primeira
célula, para quem ouve saber onde o aviso aparece. labels.selected troca o
texto quando a tela está em outro idioma.
Não há aria-selected aqui. Ele só é válido dentro de grid ou treegrid, e
num <table> simples o navegador descarta o atributo: medido na árvore de
acessibilidade, a linha expunha zero propriedades. Adotar role="grid" traria
a obrigação de navegar por setas entre as células, que a peça não implementa,
e trocaria um defeito por outro maior.
| Prop | Tipo |
|---|---|
labelsO texto do marcador da linha escolhida. | { selected?: string | undefined; } |
selected0.4.0Linha escolhida. | boolean |
Além destas, a peça aceita className, style, id e children, repassados ao elemento de baixo.