Field
templates/components/Field
O Field não é o input — é o orquestrador da anatomia e dos estados de um campo de formulário: reúne rótulo, descrição, mensagem de erro e legenda, cuida da ligação rótulo↔controle e do feedback de validação (Field:Error com role=alert e dedupe), além de agrupar campos (Group/Set). Sua particularidade é gerir semântica de formulário, obrigatoriedade e feedback de validação — nunca o visual do controle em si.
Base congelada
Capacidades 8
Conteúdo
Marcador de obrigatoriedade
Aplica a convenção de obrigatório/opcional ao…
Contador de caracteres
Exibe contagem viva "N/máx" na faixa…
Resumo de erros do grupo
No Field:Group, agrega os campos inválidos…
Marcador de alteração
Em formulários de edição, sinaliza com…
Comportamento
Momento da validação
Define QUANDO o Field:Error (já estilizado…
Dica no rótulo
Converte a Field:Description em disclosure sob…
Indicador de campo validado
Afirmação positiva: ao passar na validação,…
Campos condicionais
Revela ou oculta um Field ou…
Claude Code
Cole no Claude Code — ele acerta de primeira.
Field (UI) — Design System (Symfony UX Toolkit / shadcn)
BASE CONGELADA (não mude por instância): Orientação e layout (vertical/horizontal/responsive via data-orientation, gaps, flex), tipografia e tamanhos de rótulo/descrição/erro/legenda, a cor do estado inválido (data-[invalid=true]:text-destructive) e do desabilitado (opacity), e toda a semântica/a11y congelada: role=group, fieldset+legend, Field:Error com role=alert e deduplicação de mensagens (uma msg = texto, várias = <ul>), e os vínculos label↔input. Enrichers só somam comportamento ou conteúdo aditivo nas regiões que o Field já possui (rótulo, faixa de descrição/erro), sem tocar fonte, cor, cantos ou espaçamento da base.
USO: <twig:Field />
CAPACIDADES OPT-IN (ligue sem alterar o visual):
- Marcador de obrigatoriedade: Aplica a convenção de obrigatório/opcional ao rótulo: asterisco (*) nos obrigatórios e/ou tag textual "(opcional)" nos opcionais, derivado do atributo required do controle. [select → Asterisco (*) nos obrigatórios / Tag "(opcional)" nos opcionais / Ambos / Nenhum]- Momento da validação: Define QUANDO o Field:Error (já estilizado e congelado) aparece: só ao enviar, ao sair do campo (blur) ou enquanto digita. Muda apenas o timing do feedback, não o visual. [select → Ao enviar o formulário / Ao sair do campo (blur) / Enquanto digita]- Contador de caracteres: Exibe contagem viva "N/máx" na faixa de descrição do campo para inputs/textarea, atualizando conforme o usuário digita. O limite máximo é configurável (0 = só contar). [number]- Dica no rótulo: Converte a Field:Description em disclosure sob demanda: em vez de texto sempre visível, mostra um ícone (?) ao lado do rótulo que revela a ajuda como tooltip ou popover. [select → Descrição sempre visível / Ícone (?) com tooltip / Ícone (?) com popover]- Indicador de campo validado: Afirmação positiva: ao passar na validação, adiciona um ícone de check (usando tokens existentes) confirmando o campo, complementando o estado inválido já presente na base. Só acrescenta iconografia, sem recolorir o controle. [toggle]- Campos condicionais: Revela ou oculta um Field ou Field:Set conforme o valor de outro controle (ex.: o checkbox "Igual ao endereço de entrega" da demo esconde os campos de cobrança). Comportamento de visibilidade, sem alterar a base. [toggle]- Resumo de erros do grupo: No Field:Group, agrega os campos inválidos num resumo com contagem e links de salto para cada erro, reforçando a acessibilidade em formulários longos após o envio. [toggle]- Marcador de alteração: Em formulários de edição, sinaliza com um ponto no rótulo os campos cujo valor difere do original (dirty), ajudando o usuário a ver o que mudou antes de salvar. [toggle]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): Orientação e layout (vertical/horizontal/responsive via data-orientation, gaps, flex), tipografia e tamanhos de rótulo/descrição/erro/legenda, a cor do estado inválido (data-[invalid=true]:text-destructive) e do desabilitado (opacity), e toda a semântica/a11y congelada: role=group, fieldset+legend, Field:Error com role=alert e deduplicação de mensagens (uma msg = texto, várias = <ul>), e os vínculos label↔input
LLM / MCP
Via MCP: tool get_component com {"id": "field"} · list_capabilities("field").
Spec crua: config/ds-specs/field.json · Conectar o MCP.
Spec machine-readable (JSON)
{
"$schema_version": "1.0",
"id": "field",
"component": "Field",
"eixo": "ui",
"particularidade": "O Field não é o input — é o orquestrador da anatomia e dos estados de um campo de formulário: reúne rótulo, descrição, mensagem de erro e legenda, cuida da ligação rótulo↔controle e do feedback de validação (Field:Error com role=alert e dedupe), além de agrupar campos (Group/Set). Sua particularidade é gerir semântica de formulário, obrigatoriedade e feedback de validação — nunca o visual do controle em si.",
"base_congelada": "Orientação e layout (vertical/horizontal/responsive via data-orientation, gaps, flex), tipografia e tamanhos de rótulo/descrição/erro/legenda, a cor do estado inválido (data-[invalid=true]:text-destructive) e do desabilitado (opacity), e toda a semântica/a11y congelada: role=group, fieldset+legend, Field:Error com role=alert e deduplicação de mensagens (uma msg = texto, várias = <ul>), e os vínculos label↔input. Enrichers só somam comportamento ou conteúdo aditivo nas regiões que o Field já possui (rótulo, faixa de descrição/erro), sem tocar fonte, cor, cantos ou espaçamento da base.",
"props": [
{
"name": "orientation",
"type": "'vertical'|'horizontal'|'responsive'",
"default": "vertical",
"description": "The layout direction of the field. Defaults to `vertical` #}"
}
],
"capacidades": [
{
"id": "marcador-de-obrigatoriedade",
"nome": "Marcador de obrigatoriedade",
"descricao": "Aplica a convenção de obrigatório/opcional ao rótulo: asterisco (*) nos obrigatórios e/ou tag textual \"(opcional)\" nos opcionais, derivado do atributo required do controle.",
"controle": "select",
"opcoes": [
"Asterisco (*) nos obrigatórios",
"Tag \"(opcional)\" nos opcionais",
"Ambos",
"Nenhum"
],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/field; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "momento-da-valida-o",
"nome": "Momento da validação",
"descricao": "Define QUANDO o Field:Error (já estilizado e congelado) aparece: só ao enviar, ao sair do campo (blur) ou enquanto digita. Muda apenas o timing do feedback, não o visual.",
"controle": "select",
"opcoes": [
"Ao enviar o formulário",
"Ao sair do campo (blur)",
"Enquanto digita"
],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/field; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "contador-de-caracteres",
"nome": "Contador de caracteres",
"descricao": "Exibe contagem viva \"N/máx\" na faixa de descrição do campo para inputs/textarea, atualizando conforme o usuário digita. O limite máximo é configurável (0 = só contar).",
"controle": "number",
"opcoes": [],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (number). Ligue no configurador em /vitrine/ui/field; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "dica-no-r-tulo",
"nome": "Dica no rótulo",
"descricao": "Converte a Field:Description em disclosure sob demanda: em vez de texto sempre visível, mostra um ícone (?) ao lado do rótulo que revela a ajuda como tooltip ou popover.",
"controle": "select",
"opcoes": [
"Descrição sempre visível",
"Ícone (?) com tooltip",
"Ícone (?) com popover"
],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (select). Ligue no configurador em /vitrine/ui/field; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "indicador-de-campo-validado",
"nome": "Indicador de campo validado",
"descricao": "Afirmação positiva: ao passar na validação, adiciona um ícone de check (usando tokens existentes) confirmando o campo, complementando o estado inválido já presente na base. Só acrescenta iconografia, sem recolorir o controle.",
"controle": "toggle",
"opcoes": [],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/field; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "campos-condicionais",
"nome": "Campos condicionais",
"descricao": "Revela ou oculta um Field ou Field:Set conforme o valor de outro controle (ex.: o checkbox \"Igual ao endereço de entrega\" da demo esconde os campos de cobrança). Comportamento de visibilidade, sem alterar a base.",
"controle": "toggle",
"opcoes": [],
"posicionavel": false,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/field; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "resumo-de-erros-do-grupo",
"nome": "Resumo de erros do grupo",
"descricao": "No Field:Group, agrega os campos inválidos num resumo com contagem e links de salto para cada erro, reforçando a acessibilidade em formulários longos após o envio.",
"controle": "toggle",
"opcoes": [],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/field; comportamento via controller Stimulus. Não altera a base."
},
{
"id": "marcador-de-altera-o",
"nome": "Marcador de alteração",
"descricao": "Em formulários de edição, sinaliza com um ponto no rótulo os campos cujo valor difere do original (dirty), ajudando o usuário a ver o que mudou antes de salvar.",
"controle": "toggle",
"opcoes": [],
"posicionavel": true,
"e_funcao": true,
"como_plugar": "Capacidade opt-in (toggle). Ligue no configurador em /vitrine/ui/field; comportamento via controller Stimulus. Não altera a base."
}
],
"snippet_uso": "<twig:Field />",
"exemplo_demo": "<div class=\"w-full max-w-md\">\n <form>\n <twig:Field:Group>\n <twig:Field:Set>\n <twig:Field:Legend>Payment Method</twig:Field:Legend>\n <twig:Field:Description>\n All transactions are secure and encrypted\n </twig:Field:Description>\n <twig:Field:Group>\n <twig:Field>\n <twig:Field:Label for=\"checkout-7j9-card-name-43j\">\n Name on Card\n </twig:Field:Label>\n <twig:Input\n id=\"checkout-7j9-card-name-43j\"\n placeholder=\"Evil Rabbit\"\n required\n />\n </twig:Field>\n <twig:Field>\n <twig:Field:Label for=\"checkout-7j9-card-number-uw1\">\n Card Number\n </twig:Field:Label>\n <twig:Input\n id=\"checkout-7j9-card-number-uw1\"\n placeholder=\"1234 5678 9012 3456\"\n required\n />\n <twig:Field:Description>\n Enter your 16-digit card number\n </twig:Field:Description>\n </twig:Field>\n <div class=\"grid grid-cols-3 gap-4\">\n <twig:Field>\n <twig:Field:Label for=\"checkout-exp-month-ts6\">\n Month\n </twig:Field:Label>\n <twig:Select id=\"checkout-exp-month-ts6\">\n <option value=\"\" disabled selected>MM</option>\n <option value=\"01\">01</option>\n <option value=\"02\">02</option>\n <option value=\"03\">03</option>\n <option value=\"04\">04</option>\n <option value=\"05\">05</option>\n <option value=\"06\">06</option>\n <option value=\"07\">07</option>\n <option value=\"08\">08</option>\n <option value=\"09\">09</option>\n <option value=\"10\">10</option>\n <option value=\"11\">11</option>\n <option value=\"12\">12</option>\n </twig:Select>\n </twig:Field>\n <twig:Field>\n <twig:Field:Label for=\"checkout-7j9-exp-year-f59\">\n Year\n </twig:Field:Label>\n <twig:Select id=\"checkout-7j9-exp-year-f59\">\n <option value=\"\" disabled selected>YYY</option>\n <option value=\"2024\">2024</option>\n <option value=\"2025\">2025</option>\n <option value=\"2026\">2026</option>\n <option value=\"2027\">2027</option>\n <option value=\"2028\">2028</option>\n <option value=\"2029\">2029</option>\n </twig:Select>\n </twig:Field>\n <twig:Field>\n <twig:Field:Label for=\"checkout-7j9-cvv\">CVV</twig:Field:Label>\n <twig:Input id=\"checkout-7j9-cvv\" placeholder=\"123\" required />\n </twig:Field>\n </div>\n </twig:Field:Group>\n </twig:Field:Set>\n <twig:Field:Separator />\n <twig:Field:Set>\n <twig:Field:Legend>Billing Address</twig:Field:Legend>\n <twig:Field:Description>\n The billing address associated with your payment method\n </twig:Field:Description>\n <twig:Field:Group>\n <twig:Field orientation=\"horizontal\">\n <twig:Checkbox\n id=\"checkout-7j9-same-as-shipping-wgm\"\n checked\n />\n <twig:Field:Label\n for=\"checkout-7j9-same-as-shipping-wgm\"\n class=\"font-normal\"\n >\n Same as shipping address\n </twig:Field:Label>\n </twig:Field>\n </twig:Field:Group>\n </twig:Field:Set>\n <twig:Field:Set>\n <twig:Field:Group>\n <twig:Field>\n <twig:Field:Label for=\"checkout-7j9-optional-comments\">\n Comments\n </twig:Field:Label>\n <twig:Textarea\n id=\"checkout-7j9-optional-comments\"\n placeholder=\"Add any additional comments\"\n class=\"resize-none\"\n />\n </twig:Field>\n </twig:Field:Group>\n </twig:Field:Set>\n <twig:Field orientation=\"horizontal\">\n <twig:Button type=\"submit\">Submit</twig:Button>\n <twig:Button variant=\"outline\" type=\"button\">\n Cancel\n </twig:Button>\n </twig:Field>\n </twig:Field:Group>\n </form>\n</div>",
"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": "Orientação e layout (vertical/horizontal/responsive via data-orientation, gaps, flex), tipografia e tamanhos de rótulo/descrição/erro/legenda, a cor do estado inválido (data-[invalid=true]:text-destructive) e do desabilitado (opacity), e toda a semântica/a11y congelada: role=group, fieldset+legend, Field:Error com role=alert e deduplicação de mensagens (uma msg = texto, várias = <ul>), e os vínculos label↔input",
"mcp": {
"tool": "get_component",
"args": {
"id": "field"
}
}
}
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.