Pular para o conteúdo

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

Payment Method

All transactions are secure and encrypted

Enter your 16-digit card number

Billing Address

The billing address associated with your payment method

Conteúdo

Marcador de obrigatoriedade

Aplica a convenção de obrigatório/opcional ao…

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. posicionável

Contador de caracteres

Exibe contagem viva "N/máx" na faixa…

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). posicionável

Resumo de erros do grupo

No Field:Group, agrega os campos inválidos…

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. posicionável

Marcador de alteração

Em formulários de edição, sinaliza com…

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. posicionável

Comportamento

Momento da validação

Define QUANDO o Field:Error (já estilizado…

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.

Dica no rótulo

Converte a Field:Description em disclosure sob…

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. posicionável

Indicador de campo validado

Afirmação positiva: ao passar na validação,…

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. posicionável

Campos condicionais

Revela ou oculta um Field ou…

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.
Código gerado

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

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"
        }
    }
}
  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.