Table
templates/components/Table
A Table é uma grade de dados: um <table> semântico dentro de um container overflow-x-auto, montado por subcomponentes composáveis (Header/Body/Footer/Row/Head/Cell/Caption). Sua pele congelada já ANTECIPA comportamento de data-grid — a Row traz data-[state=selected] e has-aria-expanded:bg-muted/50, a Cell trata [role=checkbox], o tfoot vem pré-estilizado para totais e a caption fica embaixo — então os enriquecedores são comportamentais (ordenar/filtrar/paginar/selecionar/congelar) e de conteúdo aditivo, encaixando nos ganchos que a base já oferece.
Base congelada
| Invoice | Status | Method | Amount |
|---|---|---|---|
| INV001 | Paid | Credit Card | $250.00 |
| INV002 | Pending | PayPal | $150.00 |
| INV003 | Unpaid | Bank Transfer | $350.00 |
| INV004 | Paid | Credit Card | $450.00 |
| INV005 | Paid | PayPal | $550.00 |
| INV006 | Pending | Bank Transfer | $200.00 |
| INV007 | Unpaid | Credit Card | $300.00 |
| Total | $2,500.00 | ||
Capacidades 11
Conteúdo
Paginação
Quebra as linhas em páginas com…
Congelar ao rolar
Fixa a 1ª coluna e/ou o…
Customizar colunas
Menu 'Colunas' pelo qual o usuário…
Peso do texto ⚠️
Peso do cabeçalho e dos dados…
Cor do Status (token) ⚠️
Mapeia cada estado a um TOKEN…
Comportamento
Ordenar por coluna
Clicar no cabeçalho reordena as linhas…
Buscar / filtrar linhas
Campo que filtra as linhas em…
Selecionar linhas
Adiciona coluna de seleção reaproveitando o…
Exportar dados
Botão que serializa os dados da…
Linhas expansíveis
Disclosure por linha: um gatilho revela…
Totais no rodapé
Calcula agregações por coluna numérica (respeitando…
Claude Code
Cole no Claude Code — ele acerta de primeira.
Table (UI) — Design System (Symfony UX Toolkit / shadcn)
BASE CONGELADA (não mude por instância): Tipografia e layout: text-sm, caption-bottom (legenda embaixo). Espaçamento: Cell com p-2 align-middle whitespace-nowrap; Head h-10 px-2 font-medium com text-left/rtl:text-start e cor text-foreground. Estrutura semântica: table/thead/tbody/tfoot/th/td com data-slot em cada peça; container relative w-full overflow-x-auto. Estados visuais da linha (imutáveis): border-b, hover:bg-muted/50, has-aria-expanded:bg-muted/50, data-[state=selected]:bg-muted, [&_tr:last-child]:border-0. Rodapé: border-t bg-muted/50 font-medium. Convenções de a11y/locale congeladas: LTR/RTL (ltr:/rtl: e pr-0/pe-0 ao redor de [role=checkbox]), foco/semântica de tabela nativa. Nada disso (fonte, cor, cantos, padding, bordas) muda ao ligar capacidade.
USO: <twig:Table />
CAPACIDADES OPT-IN (ligue sem alterar o visual):
- Ordenar por coluna: Clicar no cabeçalho reordena as linhas e mostra seta de direção (▲/▼) na coluna ativa. Escolhe quais direções ficam habilitadas. [multi → Crescente (A→Z / 0→9) / Decrescente (Z→A / 9→0) / Restaurar ordem original]- Buscar / filtrar linhas: Campo que filtra as linhas em tempo real, escondendo as que não casam com o termo. Escopo global ou por coluna. [select → Global (todas as colunas) / Por coluna]- Paginação: Quebra as linhas em páginas com « ‹ › », 'ir para página' e contador 'N–M de T'; tamanho de página escolhível. [select → 5 por página / 10 por página / 20 por página / 50 por página / Mostrar tudo]- Selecionar linhas: Adiciona coluna de seleção reaproveitando o [role=checkbox] e o data-[state=selected] que a base já estiliza; exibe contagem 'N de M'. [select → Seleção única (radio) / Seleção múltipla (checkbox + selecionar tudo)]- Congelar ao rolar: Fixa a 1ª coluna e/ou o cabeçalho via position:sticky dentro do container overflow da base, mantendo a referência visível ao rolar. [multi → Primeira coluna / Cabeçalho (rolagem vertical)]- Exportar dados: Botão que serializa os dados da tabela nos formatos escolhidos; estilo do botão (ícone+texto / só texto / só ícone) configurável. [multi → PDF / CSV / Excel / Copiar / Imprimir]- Customizar colunas: Menu 'Colunas' pelo qual o usuário final mostra/oculta cada coluna em runtime, sem tocar no markup nem no visual da base. [toggle]- Linhas expansíveis: Disclosure por linha: um gatilho revela um painel de detalhe numa sub-linha, usando o realce has-aria-expanded:bg-muted/50 que a base já prevê. [toggle]- Totais no rodapé: Calcula agregações por coluna numérica (respeitando o filtro/página ativos) e as escreve no tfoot já pré-estilizado (bg-muted/50, font-medium) da base. [multi → Soma / Média / Contagem / Mínimo / Máximo]- Peso do texto: Peso do cabeçalho e dos dados (normal/médio/semi/negrito). É tipografia = DECISÃO DE BASE, não capacidade. [select → normal / médio / semi / negrito] (⚠️ borderline: revisar se não é decisão de base)- Cor do Status (token): Mapeia cada estado a um TOKEN semântico (success/warning/info/danger/neutral) do app.css. A cor é base; só a associação estado→token é escolhida. [select → success / warning / info / danger / neutral] (⚠️ borderline: revisar se não é decisão de base)FAÇA:
- Use <twig:Nome> — a base é congelada, você é dono do template em templates/components/.
- Ligue apenas capacidades opt-in listadas (e_funcao=true); a aparência não muda ao ligá-las.
- Passe atributos extras (id, aria-*, name, data-*) via {{ attributes }}.
NÃO FAÇA:
- Não mude cor/fonte/cantos/espaçamento por instância — é decisão de BASE, na fonte única assets/styles/app.css.
- Não crie variante/fork para a mesma coisa — existe UMA base por componente.
- Não reimplemente o componente nem adicione toolchain Node.
TESTE ANTES DE MUDAR: "é função ou é base?" — função = capacidade opt-in; base = mude o token na fonte única (assets/styles/app.css), para todos.
A11Y (herdada da base): Estrutura semântica: table/thead/tbody/tfoot/th/td com data-slot em cada peça. Estados visuais da linha (imutáveis): border-b, hover:bg-muted/50, has-aria-expanded:bg-muted/50, data-[state=selected]:bg-muted, [&_tr:last-child]:border-0. Convenções de a11y/locale congeladas: LTR/RTL (ltr:/rtl: e pr-0/pe-0 ao redor de [role=checkbox]), foco/semântica de tabela nativa
LLM / MCP
Via MCP: tool get_component com {"id": "table"} · list_capabilities("table").
Spec crua: config/ds-specs/table.json · Conectar o MCP.
Spec machine-readable (JSON)
{
"$schema_version": "1.0",
"id": "table",
"component": "Table",
"eixo": "ui",
"particularidade": "A Table é uma grade de dados: um <table> semântico dentro de um container overflow-x-auto, montado por subcomponentes composáveis (Header/Body/Footer/Row/Head/Cell/Caption). Sua pele congelada já ANTECIPA comportamento de data-grid — a Row traz data-[state=selected] e has-aria-expanded:bg-muted/50, a Cell trata [role=checkbox], o tfoot vem pré-estilizado para totais e a caption fica embaixo — então os enriquecedores são comportamentais (ordenar/filtrar/paginar/selecionar/congelar) e de conteúdo aditivo, encaixando nos ganchos que a base já oferece.",
"base_congelada": "Tipografia e layout: text-sm, caption-bottom (legenda embaixo). Espaçamento: Cell com p-2 align-middle whitespace-nowrap; Head h-10 px-2 font-medium com text-left/rtl:text-start e cor text-foreground. Estrutura semântica: table/thead/tbody/tfoot/th/td com data-slot em cada peça; container relative w-full overflow-x-auto. Estados visuais da linha (imutáveis): border-b, hover:bg-muted/50, has-aria-expanded:bg-muted/50, data-[state=selected]:bg-muted, [&_tr:last-child]:border-0. Rodapé: border-t bg-muted/50 font-medium. Convenções de a11y/locale congeladas: LTR/RTL (ltr:/rtl: e pr-0/pe-0 ao redor de [role=checkbox]), foco/semântica de tabela nativa. Nada disso (fonte, cor, cantos, padding, bordas) muda ao ligar capacidade.",
"props": [],
"capacidades": [
{
"id": "ordenar-por-coluna",
"nome": "Ordenar por coluna",
"descricao": "Clicar no cabeçalho reordena as linhas e mostra seta de direção (▲/▼) na coluna ativa. Escolhe quais direções ficam habilitadas.",
"controle": "multi",
"opcoes": [
"Crescente (A→Z / 0→9)",
"Decrescente (Z→A / 9→0)",
"Restaurar ordem original"
],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (multi). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "buscar-filtrar-linhas",
"nome": "Buscar / filtrar linhas",
"descricao": "Campo que filtra as linhas em tempo real, escondendo as que não casam com o termo. Escopo global ou por coluna.",
"controle": "select",
"opcoes": [
"Global (todas as colunas)",
"Por coluna"
],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "pagina-o",
"nome": "Paginação",
"descricao": "Quebra as linhas em páginas com « ‹ › », 'ir para página' e contador 'N–M de T'; tamanho de página escolhível.",
"controle": "select",
"opcoes": [
"5 por página",
"10 por página",
"20 por página",
"50 por página",
"Mostrar tudo"
],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "selecionar-linhas",
"nome": "Selecionar linhas",
"descricao": "Adiciona coluna de seleção reaproveitando o [role=checkbox] e o data-[state=selected] que a base já estiliza; exibe contagem 'N de M'.",
"controle": "select",
"opcoes": [
"Seleção única (radio)",
"Seleção múltipla (checkbox + selecionar tudo)"
],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "congelar-ao-rolar",
"nome": "Congelar ao rolar",
"descricao": "Fixa a 1ª coluna e/ou o cabeçalho via position:sticky dentro do container overflow da base, mantendo a referência visível ao rolar.",
"controle": "multi",
"opcoes": [
"Primeira coluna",
"Cabeçalho (rolagem vertical)"
],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (multi). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "exportar-dados",
"nome": "Exportar dados",
"descricao": "Botão que serializa os dados da tabela nos formatos escolhidos; estilo do botão (ícone+texto / só texto / só ícone) configurável.",
"controle": "multi",
"opcoes": [
"PDF",
"CSV",
"Excel",
"Copiar",
"Imprimir"
],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (multi). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "customizar-colunas",
"nome": "Customizar colunas",
"descricao": "Menu 'Colunas' pelo qual o usuário final mostra/oculta cada coluna em runtime, sem tocar no markup nem no visual da base.",
"controle": "toggle",
"opcoes": [],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "linhas-expans-veis",
"nome": "Linhas expansíveis",
"descricao": "Disclosure por linha: um gatilho revela um painel de detalhe numa sub-linha, usando o realce has-aria-expanded:bg-muted/50 que a base já prevê.",
"controle": "toggle",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "totais-no-rodap",
"nome": "Totais no rodapé",
"descricao": "Calcula agregações por coluna numérica (respeitando o filtro/página ativos) e as escreve no tfoot já pré-estilizado (bg-muted/50, font-medium) da base.",
"controle": "multi",
"opcoes": [
"Soma",
"Média",
"Contagem",
"Mínimo",
"Máximo"
],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (multi). Ligue no configurador em /vitrine/ui/table; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "peso-do-texto",
"nome": "Peso do texto",
"descricao": "Peso do cabeçalho e dos dados (normal/médio/semi/negrito). É tipografia = DECISÃO DE BASE, não capacidade.",
"controle": "select",
"opcoes": [
"normal",
"médio",
"semi",
"negrito"
],
"posicionavel": false,
"e_funcao": false,
"como_plugar": "REPROVADO no teste 'é função ou é base' — NÃO plugar como capacidade. É decisão de BASE: mude o token na fonte única assets/styles/app.css, valendo para TODOS os componentes."
},
{
"id": "cor-do-status-token",
"nome": "Cor do Status (token)",
"descricao": "Mapeia cada estado a um TOKEN semântico (success/warning/info/danger/neutral) do app.css. A cor é base; só a associação estado→token é escolhida.",
"controle": "select",
"opcoes": [
"success",
"warning",
"info",
"danger",
"neutral"
],
"posicionavel": false,
"e_funcao": false,
"como_plugar": "REPROVADO no teste 'é função ou é base' — NÃO plugar como capacidade. É decisão de BASE: mude o token na fonte única assets/styles/app.css, valendo para TODOS os componentes."
}
],
"snippet_uso": "<twig:Table />",
"exemplo_demo": "{%- set invoices = [\n {invoice: 'INV001', paymentStatus: 'Paid', totalAmount: '$250.00', paymentMethod: 'Credit Card'},\n {invoice: 'INV002', paymentStatus: 'Pending', totalAmount: '$150.00', paymentMethod: 'PayPal'},\n {invoice: 'INV003', paymentStatus: 'Unpaid', totalAmount: '$350.00', paymentMethod: 'Bank Transfer'},\n {invoice: 'INV004', paymentStatus: 'Paid', totalAmount: '$450.00', paymentMethod: 'Credit Card'},\n {invoice: 'INV005', paymentStatus: 'Paid', totalAmount: '$550.00', paymentMethod: 'PayPal'},\n {invoice: 'INV006', paymentStatus: 'Pending', totalAmount: '$200.00', paymentMethod: 'Bank Transfer'},\n {invoice: 'INV007', paymentStatus: 'Unpaid', totalAmount: '$300.00', paymentMethod: 'Credit Card'},\n] -%}\n<twig:Table>\n <twig:Table:Caption>A list of your recent invoices.</twig:Table:Caption>\n <twig:Table:Header>\n <twig:Table:Row>\n <twig:Table:Head class=\"w-[100px]\">Invoice</twig:Table:Head>\n <twig:Table:Head>Status</twig:Table:Head>\n <twig:Table:Head>Method</twig:Table:Head>\n <twig:Table:Head class=\"text-right\">Amount</twig:Table:Head>\n </twig:Table:Row>\n </twig:Table:Header>\n <twig:Table:Body>\n {% for invoice in invoices %}\n <twig:Table:Row>\n <twig:Table:Cell class=\"font-medium\">{{ invoice.invoice }}</twig:Table:Cell>\n <twig:Table:Cell>{{ invoice.paymentStatus }}</twig:Table:Cell>\n <twig:Table:Cell>{{ invoice.paymentMethod }}</twig:Table:Cell>\n <twig:Table:Cell class=\"text-right\">{{ invoice.totalAmount }}</twig:Table:Cell>\n </twig:Table:Row>\n {% endfor %}\n </twig:Table:Body>\n <twig:Table:Footer>\n <twig:Table:Row>\n <twig:Table:Cell colspan=\"3\">Total</twig:Table:Cell>\n <twig:Table:Cell class=\"text-right\">$2,500.00</twig:Table:Cell>\n </twig:Table:Row>\n </twig:Table:Footer>\n</twig:Table>",
"regras": {
"faca": [
"Use <twig:Nome> — a base é congelada, você é dono do template em templates/components/.",
"Ligue apenas capacidades opt-in listadas (e_funcao=true); a aparência não muda ao ligá-las.",
"Passe atributos extras (id, aria-*, name, data-*) via {{ attributes }}."
],
"nao_faca": [
"Não mude cor/fonte/cantos/espaçamento por instância — é decisão de BASE, na fonte única assets/styles/app.css.",
"Não crie variante/fork para a mesma coisa — existe UMA base por componente.",
"Não reimplemente o componente nem adicione toolchain Node."
]
},
"a11y": "Estrutura semântica: table/thead/tbody/tfoot/th/td com data-slot em cada peça. Estados visuais da linha (imutáveis): border-b, hover:bg-muted/50, has-aria-expanded:bg-muted/50, data-[state=selected]:bg-muted, [&_tr:last-child]:border-0. Convenções de a11y/locale congeladas: LTR/RTL (ltr:/rtl: e pr-0/pe-0 ao redor de [role=checkbox]), foco/semântica de tabela nativa",
"mcp": {
"tool": "get_component",
"args": {
"id": "table"
}
},
"props_nota": "Sem props próprias declaradas: a base repassa atributos nativos via {{ attributes }} (id, name, type, value, aria-*, data-*). Props específicas, quando existem, ficam nos subcomponentes."
}
O que mudou 1
-
v0.1.0 Camada de IA: scaffold de página + specs íntegras
- Novo tool MCP get_page_scaffold: devolve a MOLDURA de uma página nova (layout + blocks reais, contrato do renderPage, receita de rota + buildNav, tokens de layout lidos de app.css e snippet .html.twig válido) — dá pra montar página sem ler o controller/layout.
- Fonte única do scaffold em config/ds-specs/_page-scaffold.json (padrão v1.0); tokens semânticos derivados de assets/styles/app.css em runtime.
- Fix: exemplo_demo estava truncado em 1600 bytes em 5 specs (card, field, table, tabs, typography) — reconstituídos íntegros a partir dos demos, fechando todas as tags.
- Nova seção Updates (este changelog versionado) como 9º eixo do shell.
- Docs: CLAUDE.md e AI-LAYER.md citam o novo tool; contadores de tools atualizados (6 → 7).
Changelog completo em Updates.