Form Field
Em andamentoO invólucro de um campo de formulário: rótulo (Label), o controle, a dica e a mensagem de erro, ligados entre si. É a fonte única de aria-invalid do sistema.
- Função
- Formulário e ação
- Também chamado de
- campo de formulário, grupo do campo, form field
- Mora em
- app/components/molecules/FormField.tsx
- Última mudança
- 2026-10-01
Prévia
A peça ao vivo, vinda da ficha, na largura escolhida. Em larguras fixas, ela roda num quadro do tamanho de uma tela de verdade.
Na largura da página: 0px
Variantes na ficha
Lidas da ficha no build, na ordem do palco. Cada uma abre sozinha, sem o styleguide em volta — é o endereço que o palco carrega.
Quando usar
Em volta de todo Input, Select ou controle de formulário que precise de rótulo, dica ou erro — ou seja, quase sempre.
Faça
- Passe um controle como filho: é nele que o FormField põe
id,aria-invalid,aria-describedbyearia-required. - Escreva a dica como instrução ("Use o e-mail da empresa"); ela continua visível quando o erro aparece.
- Escreva o erro dizendo como acertar: "Informe um e-mail com @", não "Inválido".
Evite
- Mais de um filho sem
as="fieldset": com uma lista de filhos, o campo único não liga nenhum. Vários controles para uma pergunta são um grupo. - Limitar nome ou endereço com o
contador: gente de verdade não cabe no limite de alguém. - Mostrar o erro antes de a pessoa terminar de preencher.
Quando não usar. Para o Checkbox, o Radio e o Toggle soltos, o rótulo já vem pela prop label. Para um grupo de opções, use as="fieldset" (ou o RadioGroup, para rádios).
Nota de uso
Grupo (E59): as="fieldset" junta várias opções numa pergunta, com a dica e o erro ligados ao <fieldset>. O contador liga o "142 de 280 caracteres" ao campo — e não se limita nome nem endereço. Com asterisco nos campos, a LegendaDeObrigatorio vai uma vez no topo. O erro está ligado ao campo por aria-describedby e é anunciado por região viva. Esta é a fonte única de aria-invalid do sistema: o Checkbox, o Radio e o Toggle derivam o estado visual dela. Com dica e erro, os dois aparecem e o campo é descrito pelos dois, dica primeiro — até a E48 o erro escondia a dica, que é justamente a instrução de como acertar. Com o `ResumoDeErros` (E85), passe anunciarErro={false}: o resumo é o alerta do envio, e o erro continua ligado ao campo, com "Erro:" na frente para quem ouve.
Anatomia
As partes, na ordem em que o olho — e o teclado — passam.
- Rótulo — Um Label ligado ao controle, com asterisco se `required` e vermelho com ícone se houver erro.
- Controle — O filho — Input, Select… —, com os atributos de ligação injetados.
- Dica — `hint`, em `text-xs`, abaixo do controle.
- Contador — `contador={{ minimo, maximo }}` (E152), um ou os dois: "142 de 280 caracteres", "faltam 12 para o mínimo de 50". Sem `atual`, conta pelo valor do controle. Ligado por `aria-describedby`; acima do limite, vermelho com ícone e quanto passou; o restante é anunciado depois que a pessoa para de digitar.
- Erro — `error`, vermelho com ícone, com "Erro:" na frente para quem ouve (E85); `role="alert"` — menos com `anunciarErro={false}`, quando o formulário tem o ResumoDeErros.
- Grupo — Com `as="fieldset"`: `<fieldset>` com `<legend>`; a dica, o contador e o erro ficam ligados ao grupo, e os filhos não são clonados.
Estados
Os estados que a peça tem no código, e o que muda em cada um.
| Estado | O que muda |
|---|---|
| Padrão | Rótulo e controle; aria-describedby só se houver dica. |
| Obrigatório | required: asterisco no rótulo e aria-required no controle. |
| Com dica | A dica aparece e descreve o controle. |
| Com erro | Rótulo vermelho com ícone, mensagem com role="alert", aria-invalid no controle — que pinta a própria borda de vermelho. |
| Dica e erro | Os dois aparecem, e o controle é descrito pelos dois, dica primeiro. |
Comportamento
| Situação | O que acontece |
|---|---|
| Erro aparece | É anunciado na hora pelo role="alert". Com anunciarErro={false} (E85), não: o ResumoDeErros é o alerta, e o erro fica na descrição do campo. |
| Filho com `id` próprio | O id do filho prevalece sobre htmlFor e sobre o gerado. |
Relacionados
Como usar no código
A importação e o menor uso, lidos do arquivo. O … marca as props obrigatórias.
import { FormField } from '@/app/components/molecules/FormField';
<FormField label={…}>…</FormField>Propriedades
Lidas do código da peça no build — não escritas à mão.
| Prop | Tipo | Padrão | Obrigatória | Descrição |
|---|---|---|---|---|
label | string | — | sim | — |
as | 'div' | 'fieldset' | 'div' | não | fieldset (E59): o campo vira um grupo — <fieldset> com <legend> —, para rádios, caixas e interruptores que respondem à mesma pergunta. A dica e o erro ficam ligados ao grupo, e o leitor de tela diz o nome do grupo junto de cada opção. Sem as, é um campo só. |
htmlFor | string | — | não | Só é necessário quando o controle não é um elemento React clonável — um componente de terceiro embrulhado, por exemplo. No caso normal o FormField gera o id sozinho e liga tudo. |
required | boolean | — | não | — |
opcional | boolean | — | não | Escreve "(opcional)" no rótulo. Não use junto com required. |
disabled | boolean | — | não | Desabilita o controle (repassado a ele) e esmaece o rótulo. |
error | string | — | não | — |
anunciarErro | boolean | true | não | O erro é anunciado na hora, como alerta (padrão). Desligue quando o formulário tem o ResumoDeErros (E85): o resumo recebe o foco e é lido uma vez; com os alertas de cada campo junto, o leitor falaria tudo em sequência, sem por onde começar. O erro continua ligado ao campo. |
hint | string | — | não | — |
contador | { atual?: number; minimo?: number; maximo?: number } | — | não | Contador de caracteres (E59), ligado ao campo por aria-describedby. Com maximo ("142 de 280 caracteres"), minimo ("faltam 12 para o mínimo de 50") ou os dois (E152). Sem atual, o FormField conta sozinho, pelo valor do controle. Quem usa leitor de tela ouve quanto falta ou sobra quando para de digitar. Regra do Base: não limite nome nem endereço — gente de verdade não cabe no limite de alguém. |
classNameDoRotulo | string | — | não | Classe do rótulo (ou da legenda, no grupo). |
children | React.ReactNode | — | sim | — |
Aceita também: Omit<React.HTMLAttributes<HTMLElement>, 'children'>.
Tokens de cor
Os tokens semânticos que o código da peça usa, com o valor de hoje em app/globals.css.
| Token | Valor | Usado como |
|---|---|---|
--destructive | #BA0517 | text-destructive |
--muted-foreground | #595959 | text-muted-foreground |
Leitor de tela
O que é anunciado, e por quê.
- O ícone do erro é
aria-hidden: quem ouve já recebe o texto e o alerta. - O FormField não recebe foco; o Tab vai direto ao controle, que é lido com o rótulo, a dica e o erro.
- Uma descrição que o controle já traz (
aria-describedbypróprio) soma à da dica e do erro, primeiro a dele — conferido no teste da ficha.
O que sustenta o estado
Esta peça usa formulario, regiaoViva. O estado dela é o de quem já foi ouvido com leitor de tela nesses padrões — e não uma passada sobre esta peça em específico.
Falta ouvir: formulario, regiaoViva — ver docs/a11y-screen-reader-checklist.md.
Status e recursos
| Tipo | Recurso | Status |
|---|---|---|
| Código | app/components/molecules/FormField.tsx | Disponível |
| Ficha | Storybook (abre em outra aba) | Disponível |
| Teste de acessibilidade | axe em npm run test | No gate de todo commit |
| Leitor de tela | Passada da E33 | Falta ouvir |
| Design | Figma | Desativado até a Fase 2 |
Histórico
Os últimos commits que tocaram o arquivo, lidos do git no build.
251416b2026-10-01E159: Testimonials — o vídeo toca dentro do cartão, YouTube e Vimeo
O histórico completo está no GitHub (abre em outra aba).