# Form validation

> Zod validation rules for React forms with one message set (English or Romanian), react-hook-form glue and a pretend request.

- Type: React helper (Hooks & Utilities)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add form-validation`
- Docs: https://wingo-ui.com/components/form-validation

## Interaction

Fill the sign-up form and leave a field: its error appears under it and clears as you fix it. Submit with empty fields and every error shows at once, focus jumps to the first one. Switch the messages to the ready Romanian set, raise the password length or the minimum strength and try again. A valid form sends a pretend request and shows the result.

## Installation

```bash
npx wingo-ui@latest add form-validation
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/form-validation` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `lib/form-validation.ts`

npm dependencies: `npm i react-hook-form @hookform/resolvers zod libphonenumber-js`

Wingo UI dependencies (installed automatically):

- [Invoicing](https://wingo-ui.com/components/invoicing) ([Markdown](https://wingo-ui.com/components/invoicing.md))

## Usage

```tsx
import { z } from "zod"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { createValidators, fieldError, useZodForm } from "@/lib/form-validation"

// English messages by default; pass VALIDATION_MESSAGES_RO for the Romanian set
const v = createValidators()
const schema = z.object({ email: v.email(), cui: v.cui({ required: false }) })

export function CompanyForm({ onSave }: { onSave: (values: z.output<typeof schema>) => Promise<void> }) {
  const form = useZodForm(schema, { defaultValues: { email: "", cui: "" } })
  return (
    <form onSubmit={form.handleSubmit(onSave)} noValidate className="flex flex-col gap-4">
      <Input label="Email" type="email" autoComplete="email" {...form.register("email")} error={fieldError(form, "email")} />
      <Input label="Tax ID (CUI)" {...form.register("cui")} error={fieldError(form, "cui")} />
      <Button type="submit" loading={form.formState.isSubmitting}>Save</Button>
    </form>
  )
}
```

## API

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `messages?` | `"ro" \| "en"` | `"en"` | Demo only: which message set the validators use (the English defaults or VALIDATION_MESSAGES_RO). |
| `minLength?` | `number` | `8` | v.password({ minLength }): the shortest password accepted. |
| `minScore?` | `number` | `2` | v.password({ score: scorePassword, minScore }): the weakest strength accepted (0 skips the check, 4 is Strong). |
| `createValidators?` | `(messages?: Partial<ValidationMessages>) => Validators` |  | Zod schemas sharing one message set: text({ required, min, max }), name(), email(), password({ minLength, score, minScore }), phone({ country }), cui(), iban(), postalCode(), url(), otp(length), accept() (a ticked checkbox), number({ min, max }). Every field rule takes { required } and accepts "" when optional. v.messages is the merged set. |
| `withMatch?` | `(schema: ZodObject, field: string, confirmField: string, message?: string) => ZodType` |  | Adds "confirmField must equal field" to an object schema, reported on the confirm field (password and its confirmation). |
| `useZodForm?` | `(schema: ZodType, options?: UseFormProps) => UseFormReturn` |  | useForm with the zod resolver and the library's timing: mode onTouched (an error appears once a field is left), then it updates as you type. Input and output types come from the schema. |
| `fieldError?` | `(form: UseFormReturn, name: string) => string \| undefined` |  | The error message of a field (dotted paths work), ready for a component's error prop: error={fieldError(form, "email")}. |
| `simulateRequest?` | `<T>(ms?: number, options?: { fail?: string; value?: T; signal?: AbortSignal }) => Promise<T>` |  | A pretend request for demos and default handlers: resolves after ms (default 900), or rejects with fail as the message. |
| `DEFAULT_VALIDATION_MESSAGES?` | `ValidationMessages` |  | The English default messages createValidators merges yours over. Counted messages are functions: tooShort(min), otp(length). |
| `VALIDATION_MESSAGES_RO?` | `ValidationMessages` |  | The ready Romanian message set: pass it to createValidators for Romanian errors. |

## Examples

- **Bank details**: IBAN and postal code rules; press Check to see the errors.

## See also

- [Field](https://wingo-ui.com/components/field) ([Markdown](https://wingo-ui.com/components/field.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))
- [Password Input](https://wingo-ui.com/components/password-input) ([Markdown](https://wingo-ui.com/components/password-input.md))
- [Invoicing](https://wingo-ui.com/components/invoicing) ([Markdown](https://wingo-ui.com/components/invoicing.md))
- [useCooldown](https://wingo-ui.com/components/use-cooldown) ([Markdown](https://wingo-ui.com/components/use-cooldown.md))

---

Canonical: https://wingo-ui.com/components/form-validation
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
