Form
Wrapper acessível para react-hook-form com validação Zod e mensagens automáticas
Form é uma camada fina sobre react-hook-form que conecta rótulos, controles, descrições e mensagens de erro via contexto — sem precisar gerenciar id, aria-describedby e aria-invalid manualmente. Combine com Zod (via @hookform/resolvers/zod) para validação declarativa.
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import {
Button,
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
} from '@uranus-workspace/design-system';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const schema = z.object({
email: z.string().email('Informe um e-mail válido.'),
password: z.string().min(8, 'Mínimo de 8 caracteres.'),
});
type FormValues = z.infer<typeof schema>;
export default function FormDefault() {
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { email: '', password: '' },
});
function onSubmit(_values: FormValues) {
// Demonstração de submit — substitua pela chamada real ao back-end.
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-[340px] space-y-6">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>E-mail</FormLabel>
<FormControl>
<Input type="email" placeholder="voce@uranus.com.br" {...field} />
</FormControl>
<FormDescription>Usamos seu e-mail para entrar no workspace.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Senha</FormLabel>
<FormControl>
<Input type="password" placeholder="••••••••" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
Entrar
</Button>
</form>
</Form>
);
}
Anatomia
- Form — reexporta
FormProviderdo react-hook-form. Espalhe{...form}nele. - FormField — wrapper do
Controllerque registra o campo e provê contexto para os filhos. A proprenderrecebe{ field }para ligar ao input. - FormItem — agrupa label + controle + descrição + mensagem. Gera um
idúnico por item. - FormLabel — rótulo que herda o
htmlForcorreto automaticamente; vira vermelho quando há erro. - FormControl —
Slotque injetaid,aria-describedbyearia-invalidno input real (Input,Textarea,Select, etc.). - FormDescription — texto auxiliar, referenciado via
aria-describedby. - FormMessage — mensagem de erro do campo. Renderiza
nullquando não há erro; quando há, exibeerror.messagedo resolver. - useFormField — hook interno que expõe
id,name,errore os*Iddo item atual. Útil se você construir controles compostos.
Uso
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import {
Button,
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
} from '@uranus-workspace/design-system';
const schema = z.object({
email: z.string().email('Informe um e-mail válido.'),
password: z.string().min(8, 'Mínimo de 8 caracteres.'),
});
type FormValues = z.infer<typeof schema>;
export function LoginForm() {
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { email: '', password: '' },
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit((values) => console.log(values))}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>E-mail</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormDescription>Usamos para entrar no workspace.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Entrar</Button>
</form>
</Form>
);
}Faça
- Sempre defina um schema Zod e passe via
zodResolver— as mensagens viram a fonte da verdade. - Use
defaultValuesem todos os campos para garantir que eles sejam controlados desde o mount (evita warnings do React). - Mantenha o
FormMessageao lado de cada campo — ele só renderiza quando há erro. - Dispare
form.handleSubmitnoonSubmitdo<form>, nunca chame o handler direto.
Não faça
- Não combine
FormcomuseStatepara valores — o react-hook-form já gerencia o estado. - Não faça validação manual dentro do
render. Coloque toda regra no schema. - Não omita
FormControl. Sem ele, os atributos de acessibilidade não são aplicados no input.
Acessibilidade
FormLabelrecebehtmlForautomaticamente com base noiddoFormItem.FormControlinjetaaria-describedbyapontando para oFormDescriptione, quando há erro, também para oFormMessage.aria-invalidé setado quando o resolver marca o campo como inválido.- Mensagens de erro são renderizadas como
<p role="alert">de fato via o elemento parágrafo — para anúncios mais agressivos em fluxos críticos, adicionerole="alert"manualmente noFormMessage. - Ao enviar o formulário, foque o primeiro campo inválido com
form.setFocus(name)para ajudar usuários de teclado e leitores de tela.