---
fonte: 01-canonicos/seed-componentes.md
versao_da_fonte: v1.43
secao: 50
titulo: "Agrupamento de linhas — `estável` (F7.2, 2026-08-15) · AG1–AG8 · gabarito A2"
sequencia: 52 de 98
bytes_do_corpo: 4811
md5_do_corpo: c0037b8824b7aa84f39ed5bf2463aeeb
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
---
## 50. Agrupamento de linhas — `estável` (F7.2, 2026-08-15) · AG1–AG8 · gabarito A2

> **Base (3 rodadas, 2026-08-15):** **R1 canon** — Fiori (agrupamento existe nas tabelas
> responsiva e analítica, com formato de cabeçalho de grupo declarável; a régua de
> personalização é por número de colunas, ~20) · Polaris IndexTable (*subheaders* com props de
> acessibilidade próprias) · Oracle OTM (seleção pelo cabeçalho de grupo abrange o grupo inteiro) ·
> HTML nativo (`<tbody>` como rowgroup, `th scope="rowgroup"/"colgroup"`). **R2 stack** — TanStack
> `grouping` com `groupedColumnMode: 'reorder' | 'remove'`. **R3 normas** — MDN `row` role
> (`aria-expanded` em linha pertence ao padrão **grid/treegrid**, não à tabela estática) ·
> **Adrian Roselli, "Table with Expando Rows"** (o padrão funciona em `<table>` nativa: disclosure
> `<button>` com `aria-expanded`, `display:table-row` × `none`, linhas reveladas DEPOIS do
> controle na ordem do código, `aria-controls` opcional — JAWS retirou o anúncio em 2019) ·
> APG treegrid (*"o código deste exemplo não é para produção"*) + Roselli (*treegrid é widget
> composto, suporte pobre, "não vi em produção"*) · JAWS 2024 issue #791 (**tabela aninhada não
> navega** em JAWS + Chrome/Edge; NVDA navega).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| AG1 | **Um `<tbody>` por grupo** — rowgroup NATIVO. O cabeçalho de grupo é uma `<tr>` com um único `<th colspan scope="colgroup">` | A semântica de agrupamento já existe no HTML; ARIA nova aqui seria "bad ARIA" sobre estrutura que o browser já expõe | `<div role="rowgroup">` (recria o que é nativo) · tabela dentro de tabela (**JAWS 2024 não navega** — #791) |
| AG2 | **O disclosure é um `<button aria-expanded>` DENTRO do cabeçalho de grupo — nunca `aria-expanded` na `<tr>`** | `aria-expanded` na linha empurra a tabela para o padrão **grid/treegrid** (MDN), e o **DT1 proíbe grid na v1**. O botão é disclosure puro, padrão com suporte universal | `role="treegrid"` (APG: não é para produção; suporte pobre) · linha inteira clicável (anti-padrão do DT1) |
| AG3 | **Nome acessível do disclosure = rótulo + contagem** (`"Metalúrgica Andrade, 3 faturas"`), e o estado vem do `aria-expanded` — nunca do texto | Roselli: o leitor de tela **não anuncia quantas linhas apareceram**; a contagem no nome administra a expectativa antes do clique | Nome só com o rótulo (o usuário expande às cegas) · anunciar "expandido/recolhido" no texto (duplica o que a AT já diz) |
| AG4 | **As linhas do grupo vêm DEPOIS do controle na ordem do código**, e o par de visibilidade é **`display:table-row` × `display:none`** | `display:block` numa `<tr>` **destrói a semântica de linha** (Roselli, *Tables, CSS Display Properties, and ARIA*); e linha revelada acima do controle deixa o usuário de AT sem saber onde ela apareceu | `visibility:hidden` (ocupa espaço) · `hidden` no atributo (mesma coisa, mas some da guarda CSS) |
| AG5 | **`aria-controls` é OPCIONAL e declarado como tal** — presente aqui, com a nota de que JAWS retirou o anúncio em 2019 e sua ausência não cria barreira | Manter a relação declarada custa nada e serve se o suporte voltar; prometer que ela funciona hoje seria falso | Depender de `aria-controls` para a navegação funcionar |
| AG6 | **Contador junto ao rótulo (C26), com o ZERO EXPLÍCITO** — `0` aparece, não some | Zero é informação: *"este grupo existe e está vazio"* é diferente de *"este grupo não existe"*. Medição do produto de referência: eles mostram `NOVO CLIENTE 0` | Esconder o contador no zero (o usuário passa a duvidar se o grupo sumiu) |
| AG7 | **Grupo de contagem zero revela o vazio de escala mínima (Z3)** — uma linha `"Nenhum item neste grupo"`, nunca o nada | Botão que não revela nada é botão morto — o mesmo defeito que o §25 já proíbe em preview, aqui em produção | Disclosure desabilitado (esconde a razão) · grupo zero oculto (contradiz o AG6) |
| AG8 | **Um nível de agrupamento na v1.** Subgrupo entra por demanda, com pesquisa própria | `<tbody>` **não aninha**, e a saída seria tabela dentro de tabela — que reprova em JAWS (#791). A profundidade que o ERP pede hoje é 1 | Agrupamento em árvore dentro da tabela (é outro arquétipo: `treegrid`, descartado no AG2) |

**Seleção dentro do grupo:** ver a extensão do §41 (SL6). **Pares de cor:** todos herdados —
cabeçalho de grupo em `surface-subtle` com `text-primary` (**12,63** claro / **14,02** escuro),
contador em `text-muted` (**5,61** / **6,80**), subtotal em `text-secondary` (**7,13** / **9,10**).
*(Instrumento: `contraste-f72.py`; comportamento: `suite-container.mjs`, guardas AG-01…AG-08.)*

---

