# How to Implement Form Validation Using Zod with the Field Component in refine-shadcn

> Master form validation with Zod and the Field component in refine-shadcn. Connect Zod schemas to useForm using zodResolver for automatic input validation and error display. Boost your app's reliability.

- Repository: [Ferdi ÜNAL/refine-shadcn](https://github.com/ferdiunal/refine-shadcn)
- Tags: how-to-guide
- Published: 2026-03-01

---

**Use `zodResolver` from `@hookform/resolvers/zod` to connect your Zod schema to `useForm` from `@refinedev/react-hook-form`, then spread the returned form object onto the `Field` component to automatically validate inputs and display error messages.**

The `refine-shadcn` library (available at `ferdiunal/refine-shadcn`) provides a streamlined approach to form validation by integrating Zod schemas with a custom `Field` component. This architecture leverages `react-hook-form` under the hood while exposing a declarative API that works seamlessly with Shadcn UI components to handle complex validation logic.

## Understanding the Validation Flow

The implementation follows a five-step architecture that bridges Zod validation with UI rendering:

1. **Schema Definition** – You define validation rules using `z.object()` in a Zod schema, specifying constraints like `.min()`, `.email()`, or `.max()` with custom error messages.
2. **Resolver Integration** – The `zodResolver` adapter (from `@hookform/resolvers/zod`) translates Zod validation errors into the format expected by `react-hook-form`.
3. **Form State Initialization** – `useForm` from `@refinedev/react-hook-form` receives the resolver and creates the form control object containing `control`, `handleSubmit`, and `formState`.
4. **Field Registration** – The `Field` component (implemented in [`packages/theme/src/components/field.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/field.tsx)) receives the form context via spread props and registers individual inputs using `react-hook-form`'s `Controller`.
5. **Error Rendering** – The `Field` component automatically renders `FormMessage` (from [`packages/theme/src/ui/form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/ui/form.tsx)) which pulls validation errors from `formState.errors` and displays them with appropriate ARIA attributes.

## Step 1: Define Your Zod Schema

Create a Zod schema that describes the shape of your form data and validation requirements. This schema enforces type safety and provides detailed error messages for each validation rule.

```typescript
import * as z from "zod";

const userSchema = z.object({
    firstName: z.string().min(2, { 
        message: "Firstname must be at least 2 characters." 
    }),
    lastName: z.string().min(2, { 
        message: "Lastname must be at least 2 characters." 
    }),
    email: z.string().email({ 
        message: "Please enter a valid email address." 
    }),
});

```

## Step 2: Connect Zod to react-hook-form

Initialize your form using `useForm` from `@refinedev/react-hook-form` and pass the `zodResolver` adapter with your schema. The `mode: "all"` configuration ensures validation runs on both change and blur events.

```typescript
import { useForm } from "@refinedev/react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

const UserForm = () => {
    const form = useForm<z.infer<typeof userSchema>>({
        mode: "all",
        resolver: zodResolver(userSchema),
        defaultValues: {
            firstName: "",
            lastName: "",
            email: "",
        },
    });

    const onSubmit = (data: z.infer<typeof userSchema>) => {
        console.log(data);
    };

    return (
        // Form JSX will be implemented in the next step
        <form onSubmit={form.handleSubmit(onSubmit)} />
    );
};

```

## Step 3: Implement the Field Component

The `Field` component from `@ferdiunal/refine-shadcn` abstracts the complexity of `react-hook-form`'s `Controller`. It accepts the form context via spread props and uses a render prop pattern to pass field handlers to your input components.

```tsx
import { Field, Form } from "@ferdiunal/refine-shadcn";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";

<Form {...form}>
    <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <Field {...form} name="firstName" label="Firstname">
            {(field) => <Input {...field} placeholder="Firstname" />}
        </Field>

        <Field {...form} name="lastName" label="Lastname">
            {(field) => <Input {...field} placeholder="Lastname" />}
        </Field>

        <Field {...form} name="email" label="Email">
            {(field) => <Input {...field} placeholder="Email" type="email" />}
        </Field>

        <Button type="submit">Submit</Button>
    </form>
</Form>

```

The `Field` component automatically handles:
- **Label rendering** via the `label` prop
- **Error message display** through `FormMessage`, which reads from `formState.errors`
- **Description text** (optional) for helper text below inputs
- **ARIA attributes** for accessibility, managed by the underlying `FormField` implementation

## Key Source Files

Understanding the underlying implementation helps when customizing validation behavior or troubleshooting issues:

- **[`packages/theme/src/components/field.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/field.tsx)** – Implements the `Field` component, wrapping `react-hook-form`'s `Controller` and rendering `FormLabel`, `FormControl`, and `FormMessage`.

- **[`packages/theme/src/components/form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/form.tsx)** – Provides the `Form` wrapper component that injects the `FormProvider` context, making form state available to all child components.

- **[`packages/theme/src/ui/form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/ui/form.tsx)** – Houses the low-level UI primitives (`FormItem`, `FormLabel`, `FormMessage`) that handle styling, error display, and ARIA attributes.

- **[`templates/vite-react/src/pages/users/Form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/templates/vite-react/src/pages/users/Form.tsx)** – Production-ready reference implementation demonstrating Zod validation with the `Field` component in a real-world user management form.

## Summary

- **Zod schemas** define validation rules and custom error messages using methods like `.min()`, `.email()`, and `.max()`.

- **`zodResolver`** bridges Zod with `react-hook-form`, translating schema validation errors into the format expected by the form state manager.

- **The `Field` component** from `@ferdiunal/refine-shadcn` abstracts the `react-hook-form` `Controller`, automatically wiring up validation, labels, and error messages through a render prop pattern.

- **Error display** happens automatically via `FormMessage` inside the `Field` component, which reads from `formState.errors` and renders appropriate feedback without additional configuration.

- **Source files** in `packages/theme/src/components/` provide the implementation details for customization, while [`templates/vite-react/src/pages/users/Form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/templates/vite-react/src/pages/users/Form.tsx) serves as the canonical usage example.

## Frequently Asked Questions

### Can I use validation libraries other than Zod with the Field component?

Yes. The `Field` component accepts any resolver compatible with `react-hook-form`, including Yup, Joi, or custom validators. Simply replace `zodResolver` with your preferred resolver (such as `yupResolver` from `@hookform/resolvers/yup`) and ensure your validation schema matches the expected data structure. The `Field` component will automatically display errors regardless of which resolver generates them.

### How do I display custom error messages for specific validation rules?

Define custom messages directly in your Zod schema using the `.min()`, `.max()`, `.email()`, or `.refine()` methods with a `message` property. The `Field` component automatically passes these messages to `FormMessage`, which renders them when validation fails. For example: `z.string().min(5, { message: "Title must be at least 5 characters" })`. You can also override messages globally by customizing the `FormMessage` component in [`packages/theme/src/ui/form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/ui/form.tsx).

### Does the Field component support nested form values or array fields?

Yes. Use dot notation for nested objects (e.g., `name="profile.bio"`) or bracket notation for arrays (e.g., `name="tags[0]"`). The `Field` component registers these paths with `react-hook-form`'s `Controller`, and Zod validates them according to your schema definition using `z.object()` for nested shapes or `z.array()` for collections. Ensure your `defaultValues` structure matches the nested path structure to avoid undefined errors.

### How do I customize the styling of validation error messages?

The error messages render through the `FormMessage` component located in [`packages/theme/src/ui/form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/ui/form.tsx). You can customize the appearance by modifying the `FormMessage` implementation to use different Tailwind classes or by wrapping the `Field` component and intercepting `formState.errors` to render custom error UI. The default implementation uses standard Shadcn UI styling with destructive color variants (`text-destructive`) for error states and automatically manages ARIA attributes for accessibility.