Formulários
Monte formulários que dizem o que deu errado e onde. A ligação do Field, a validação, o resumo de erros e a receita com react-hook-form.
// você vai aprender
- O que o
Fieldliga para você, e o que fica com a sua marcação - Dois jeitos de validar: no campo, ou com o seu próprio estado
- Quando usar um
ErrorSummary, e para onde vai o foco depois de um envio com erro - Como ligar react-hook-form e zod sem perder nada disso
Ligue um campo
O Field liga um rótulo, uma descrição e um erro ao controle que está dentro dele. O FieldLabel vira um <label> do controle, e FieldDescription e FieldError são ligados por aria-describedby, então o leitor de tela lê a dica e o erro quando o controle recebe foco.
import { Field, FieldDescription, FieldError, FieldLabel, Input } from "@camaleo/react";
<Field validationMode="onBlur">
<FieldLabel>E-mail de trabalho</FieldLabel>
<Input type="email" autoComplete="email" required />
<FieldDescription>Respondemos em até um dia útil.</FieldDescription>
<FieldError match="valueMissing">Informe seu e-mail de trabalho.</FieldError>
</Field>
Input, Textarea, NativeSelect, Select, Combobox, NumberField, Checkbox, Switch, Slider e os campos de data entram no Field em que estão. Agrupe campos relacionados com Fieldset e FieldsetLegend. Para um checkbox ou switch sozinho, um Label em volta basta.
Valide no campo
O Field roda as verificações do navegador (required, type="email", minLength…) e a sua função validate. O FieldError mostra a mensagem; match escolhe qual falha ele informa, e o resultado do validate aparece como está.
O validationMode define quando a verificação acontece. O padrão do Base UI, "onSubmit", foi feito para o componente Form dele, que o Camaleo não embrulha, então defina "onBlur" ou "onChange" no Field, ou deixe uma biblioteca de formulário decidir (abaixo).
Letras minúsculas, números, pontos e sublinhados.
<Field
validationMode="onChange"
validate={(value) => (String(value ?? "").length < 3 ? "Use pelo menos 3 caracteres." : null)}
>
<FieldLabel>Nome de usuário</FieldLabel>
<Input autoComplete="username" />
<FieldError />
</Field>
Quando o resultado vem de outro lugar (um servidor, uma biblioteca de formulário), defina invalid no Field e passe o mesmo valor para o match do FieldError, que então mostra o conteúdo dele como está. É assim que a receita abaixo funciona.
Escreva erros que dizem como resolver: "Informe um e-mail como ana@empresa.com.br", não "Entrada inválida". Cada página de componente tem um par de faz/não faz sobre isso.
Resuma os erros
Quando o formulário tem mais que poucos campos, um envio com erro deve cair num ErrorSummary no topo: um alerta de perigo cujo título diz quantos campos precisam de correção, com um link por erro. Cada link leva o foco ao controle.
const summary = useRef<HTMLDivElement>(null);
// Depois de um envio com erro, foque o resumo para ele ser lido primeiro.
useEffect(() => {
if (failedAttempts > 0) summary.current?.focus();
}, [failedAttempts]);
{errors.length > 0 && (
<ErrorSummary
ref={summary}
tabIndex={-1}
title={`Corrija ${errors.length} campos para continuar.`}
errors={errors.map((e) => ({ target: e.id, message: e.message }))}
/>
)}
O target é o id do controle. Gere cada id com useId(), para duas cópias do formulário na mesma página nunca colidirem.
Use react-hook-form e zod
O react-hook-form guarda os valores e roda o schema; o Camaleo mostra o resultado. Envolva cada controle num Controller, passe fieldState.invalid para o Field e espalhe field no controle:
const { control, handleSubmit } = useForm<Values>({
resolver: zodResolver(schema),
defaultValues: { email: "" },
// O ErrorSummary recebe o foco, não o primeiro campo inválido.
shouldFocusError: false,
});
<Controller
name="email"
control={control}
render={({ field, fieldState }) => (
<Field invalid={fieldState.invalid}>
<FieldLabel>E-mail de trabalho</FieldLabel>
<Input {...field} id={ids.email} type="email" autoComplete="email" />
<FieldError match={fieldState.invalid}>{fieldState.error?.message}</FieldError>
</Field>
)}
/>
Use shouldFocusError: false quando o formulário tiver um ErrorSummary. Senão o react-hook-form leva o foco antes para o primeiro campo inválido, e quem usa leitor de tela nunca ouve o resumo.
O Checkbox informa checked em vez de value, então ligue à mão: checked={field.value}, onCheckedChange={field.onChange} e ref={field.ref}.
Um formulário completo
Tudo acima num formulário só: cinco campos, um schema, o resumo com foco e uma confirmação que recebe o foco quando o formulário some. Envie vazio e depois corrija os campos um a um. Abra a aba Code para ver o arquivo inteiro.
Antes de publicar
- Todo controle tem um rótulo visível, não só um placeholder
- Cada erro diz como resolver, no campo e no resumo
- Depois de um envio com erro, o foco vai para o resumo, e cada link chega ao campo
noValidateno<form>, para os balões do navegador não competirem com as suas mensagens- Os ids vêm de
useId(), e dados pessoais têmautoComplete