---
fonte: 01-canonicos/seed-componentes.md
versao_da_fonte: v1.43
secao: 40
titulo: "Tabela — `estável` (v0.43) · DT1–DT8 · Bloco 6/6A · lote autônomo 2"
sequencia: 42 de 98
bytes_do_corpo: 4979
md5_do_corpo: f845e30e66caf6e9a2e297d3ec2c5fec
gerado_por: 06-validacao/geradores/gen-camada-ia.py
nota: fatia GERADA — o corpo abaixo é byte a byte o trecho do canônico; edite o canônico, nunca esta fatia. Canônico inteiro em https://ds.seed.eng.br/01-canonicos/seed-componentes.md
---
## 40. Tabela — `estável` (v0.43) · DT1–DT8 · Bloco 6/6A · lote autônomo 2

> **Base (3 rodadas, 2026-08-06):** R1 — Carbon data table (alturas de linha pareadas com a toolbar; texto à esquerda incl. headers, numérico variável à direita, identificadores discretos à esquerda; sort por caret; inline actions por overflow; quando NÃO usar: substituto de planilha) + NN/g herdado. R2 — **TanStack Table** (a base da stack Lovable/shadcn) + EUI. R3 — **corpus Adrian Roselli** ("Don't Turn a Table into an ARIA Grid Just for a Clickable Row" 2023; Sortable Table Columns 2021–24: botão no th + `aria-sort` sem role extra, gap de anúncio de NVDA → live region OPCIONAL desligada; A Responsive Accessible Table: wrapper `role=region`+`tabindex=0` nomeado; multi-sort = lacuna do ARIA #283) + **Fiori** (responsive table = nível de LINHA, ≤200 itens; grid table = desktop cell-level >1000, NÃO-responsiva — a nossa §40 segue a filosofia responsive; importância de coluna/pop-in ficam para o §43/6D).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| DT1 | **`<table>` NATIVA — nunca `role="grid"` na v1:** caption sempre; `th scope="col"`; wrapper de rolagem `role="region"` + `aria-labelledby` + `tabindex="0"`. Grid real (edição em célula/2D) = componente FUTURO com pesquisa própria | Roselli 2023 (grid por linha clicável é o anti-padrão); Fiori (grid table é outra classe, não-responsiva); a semântica nativa dá leitura de cabeçalho de graça | role=grid "para o teclado ficar rico" (quebra a leitura de tabela dos SRs); div-table |
| DT2 | **3 densidades por atributo** — confortável 48 (default) · densa 40 · compacta 32 — **com trava touch: <640px qualquer densidade volta a ≥44px** (a pendência ERP→mobile do B2 se resolve aqui); na compacta, os CONTROLES internos reduzem junto (o piso é deles — achado da render R9) | Carbon (4 alturas; pareamento com toolbar); paridade mobile (premissa estrutural); medição renderizada 49/41/33 | Densidade livre por CSS do consumidor; compacta no touch |
| DT3 | **Alinhamento:** texto à esquerda (headers junto); **numérico variável à direita em JetBrains Mono**; identificador discreto (Nº) à esquerda em mono `text-secondary`; datas à esquerda; nunca centralizar coluna | Carbon literal (incl. a exceção do identificador); mono = regra da marca p/ dados | Centralizar; header desalinhado da coluna |
| DT4 | **Zebra OPCIONAL (default OFF)** — hover de linha + borda de linha são o default; zebra recomendada editorialmente em tabelas largas (≥6 colunas) e **nunca sobrepõe a linha selecionada** | Carbon (opcional); o hover+divider cobre o rastreio na largura típica do ERP | Zebra default (ruído na tabela estreita) |
| DT5 | **Ordenação:** BOTÃO dentro do `th` + `aria-sort` no th (ÚNICA — multi-sort é lacuna do ARIA #283, fora da v1); ciclo asc↔desc (sem 3º estado de remoção); **seta SEMPRE visível** (neutra esmaecida ↕, ativa colorida ↑↓); **live region de ordenação DESLIGADA por default** (o `aria-sort` é o canal; o gap do NVDA é conhecido — ligável por prop, decisão do produto) | Roselli integral (incl. o trade-off do anúncio, documentado em teste com JAWS/NVDA/VO); Carbon | th clicável cru (sem botão = sem teclado); seta só no hover (affordance invisível); multi-sort |
| DT6 | **Cabeçalho fixo:** `position:sticky` no th com fundo próprio (pares medidos 6.03/8.92), z 1 (abaixo de tudo do sistema); **`border-collapse:separate`** obrigatório (collapse quebra o sticky — bug de engine, guarda DT-13) | Roselli fixed headers; errata pega pela render R9 | Header que some no scroll de tabela longa |
| DT7 | **Célula:** wrap por default; truncamento é decisão POR COLUNA com acesso ao valor completo; **célula vazia mostra "—"** (nunca muda); badge §21 para status; ações de linha = overflow "⋯" §35 | Roselli (célula vazia anuncia); Carbon (inline actions) | Truncar tudo com tooltip (Fiori: tooltip não existe no toque) |
| DT8 | **Fronteiras:** ordenação/filtro no servidor, virtual scroll, >~200 linhas por página, export = produto; edição em célula = grid futuro; degradação mobile por importância de coluna (pop-in Fiori) = **§43/6D**; toolbar de busca/filtros = **§42/6C** | Fiori (≤200 na responsive; grid p/ milhares); recorte do lote | Especificar tudo num § só |

**Tokens/pares (`contraste-dados.py` ✅):** header ci-700/ci-50 **6.03** light · ci-300/sunken **8.92** dark; seta ativa tq-600/ci-50 **4.23** (≥3 ✓); demais herdados. Os 7 testes: 1 medidos ✓ · 2 ordenação por seta+aria-sort (nunca só cor) · 3 grayscale ok (setas/peso) · 4 confortável×compacta a 1s · 5 uma coluna ordenada, um header fixo · 6 estado de ordenação visível sem interação · 7 360px ≥44 forçado + rolagem do wrapper focável. *(Suites: jsdom 6A ✓ · render R9: sticky colado e imóvel, densidades 49/41/33, 360 ✓.)*

---

