# Form shadcn con react-hook-form

Dichiari lo schema Zod, monti Form con useForm e lasci che FormField colleghi input, errore e attributi ARIA. Le classi shadcn restano intatte.

> Pagina: https://ui.eneawork.it/blog/shadcn-form-react-hook-form
> Markdown: https://ui.eneawork.it/blog/shadcn-form-react-hook-form.md

*2026-11-01*

Risposta breve: installa react-hook-form, zod e @hookform/resolvers, dichiara lo schema e monta Form con useForm. Ogni FormField collega input, messaggio di errore e attributi ARIA.

I pesi sono dichiarati su npm e misurati il 1 novembre 2026: react-hook-form 7.91.0 dichiara 1.639.036 byte non compressi, zod 4.6.7 ne dichiara 6.148.944, perché il pacchetto contiene ogni build e ogni locale. Entrambi hanno licenza MIT. Al browser arriva il bundle, non il tarball.

## Lo schema prima del form

import { z } from "zod"; export const signupSchema = z.object({ email: z.email(), password: z.string().min(12, "Almeno 12 caratteri"), }); export type SignupValues = z.infer;

Lo schema fa due lavori: valida i valori a runtime e produce il tipo SignupValues a compile time. Il resolver traduce il risultato di Zod nel formato che react-hook-form si aspetta, quindi gli errori arrivano già associati al campo.

## Il form completo

import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { signupSchema, type SignupValues } from "./schema"; export function SignupForm() { const form = useForm({ resolver: zodResolver(signupSchema), defaultValues: { email: "", password: "" }, mode: "onBlur", }); return ( console.log(values))} noValidate> ( Email )} /> Crea account ); }

Tre dettagli tengono in piedi il tutto: Form pubblica il contesto del form, FormControl aggiunge aria-invalid e aria-describedby quando c'è un errore, e FormMessage stampa il messaggio solo se quel campo è invalido.

## Cosa fa FormField al posto tuo

## Il comando

npx shadcn@latest add form input button npm install react-hook-form zod @hookform/resolvers

## Record vivi nel catalogo

Il risultato si confronta con record già pubblicati: la libreria shadcn, il blocco form-layout-01 con etichette e colonne, e la categoria blocchi form. Ogni record indica licenza e sorgente.

## La limitazione

Lo schema valida nel browser, non sul server: la stessa regola va rieseguita lì, altrimenti basta una richiesta costruita a mano per saltarla. Il form monta con noValidate, quindi la validazione nativa del browser resta spenta e i messaggi sono solo quelli che scrivi tu.

## Dove continuare

L'installazione dei componenti è in come installare shadcn/ui; la stessa validazione lato server è in Server Actions e validazione; il confronto fra le librerie di form è in librerie di form React. Per i token di stile leggi come tematizzare shadcn/ui.

## FAQ

### Devo installare react-hook-form e zod a mano?

Sì. Il comando shadcn aggiunge solo i componenti di interfaccia; react-hook-form, zod e il resolver sono dipendenze del progetto e vanno installati con npm.

### Posso usare Form senza Zod?

Sì. Form accetta qualsiasi resolver compatibile con react-hook-form, per esempio Yup o Valibot, oppure le regole inline. Zod aggiunge il tipo dedotto dallo schema, che con le regole inline non hai.

### Perché FormMessage resta vuoto?

Perché il campo non è ancora stato validato, di solito per un mode troppo pigro o per il nome che non combacia. Controlla il mode di useForm e il name di FormField.

### Come mostro un errore che arriva dal server?

Con form.setError sul nome del campo, dopo la risposta. Il messaggio entra nello stesso canale di FormMessage, quindi non serve un secondo componente per gli errori remoti.

### Il form funziona senza JavaScript?

No. react-hook-form valida e invia nel browser, quindi senza JavaScript il submit non parte. Per un form che funziona senza JavaScript serve un form HTML con action lato server.
