Form Validation Library
Battle-tested form patterns built on React Hook Form and Zod that handle the hard parts of form development: multi-step wizards with state persistence, async field validation, file upload with drag-and-drop, dependent field logic, accessible error announcements, and type-safe form schemas that double as API request validators. Includes 20+ ready-to-use form templates covering authentication, checkout, profile editing, surveys, and admin CRUD — each with full TypeScript types and zero runtime dependencies beyond React Hook Form and Zod.
Key Features
Zod Schema Validation — Type-safe schemas that validate on the client and reuse on the server; define once, validate everywhere
Multi-Step Form Wizard — Step navigation with validation-per-step, progress persistence to sessionStorage, and back/forward support
Accessible Error Handling — Errors announced to screen readers viaaria-live, focus management on submission failure, and inline validation
File Upload Patterns — Drag-and-drop zone with preview, file type/size validation, multi-file uploads, and upload progress tracking
Dependent Fields — Watch-based conditional rendering where field B's options change based on field A's value, with proper cleanup
Async Validation — Debounced server-side validation (e.g., "is this username taken?") with loading indicators
Form State Persistence — Auto-save drafts to localStorage with configurable debounce, restore on page reload
Server Action Integration — Patterns for Next.js Server Actions with progressive enhancement and optimistic UI
Quick Start
- Install dependencies:
npm install react-hook-form zod @hookform/resolvers
Copy the
forms/directory into your project'ssrc/lib/folder.Create a validated form in under 30 lines:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { FormField, FormError } from '@/lib/forms/components';
const loginSchema = z.object({
email: z.string().email('Enter a valid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
type LoginForm = z.infer<typeof loginSchema>;
export function LoginForm({ onSubmit }: { onSubmit: (data: LoginForm) => void }) {
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<LoginForm>({
resolver: zodResolver(loginSchema),
});
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<FormField label="Email" error={errors.email}>
<input type="email" {...register('email')} aria-invalid={!!errors.email} />
</FormField>
<FormField label="Password" error={errors.password}>
<input type="password" {...register('password')} aria-invalid={!!errors.password} />
</FormField>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Signing in...' : 'Sign In'}
</button>
</form>
);
}
Architecture / How It Works
form-validation-library/
├── schemas/ # Zod validation schemas (auth, profile, checkout, common)
├── components/ # FormField, FormError, FileUpload, MultiStepForm, AsyncSelect
├── hooks/ # useFormPersist, useAsyncValidation, useDependentFields
├── templates/ # LoginForm, RegistrationWizard, CheckoutForm, ContactForm
└── utils/ # Formatting and parsing utilities
Usage Examples
Multi-Step Form Wizard
import { MultiStepForm, Step } from '@/lib/forms/components';
import { personalInfoSchema, addressSchema, paymentSchema } from '@/lib/forms/schemas';
export function CheckoutWizard() {
return (
<MultiStepForm
onComplete={async (data) => {
await fetch('/api/orders', { method: 'POST', body: JSON.stringify(data) });
}}
persistKey="checkout-draft"
>
<Step schema={personalInfoSchema} title="Personal Info">
{({ register, errors }) => (
<>
<FormField label="Full Name" error={errors.name}>
<input {...register('name')} />
</FormField>
<FormField label="Email" error={errors.email}>
<input type="email" {...register('email')} />
</FormField>
</>
)}
</Step>
<Step schema={addressSchema} title="Shipping Address">
{({ register, errors }) => (/* address fields */)}
</Step>
<Step schema={paymentSchema} title="Payment">
{({ register, errors }) => (/* payment fields */)}
</Step>
</MultiStepForm>
);
}
Configuration
Zod Custom Error Messages
// schemas/common.ts — reusable field validators
export const emailField = z
.string()
.min(1, 'Email is required')
.email('Enter a valid email address')
.transform((v) => v.toLowerCase().trim());
export const passwordField = z
.string()
.min(8, 'Must be at least 8 characters')
.regex(/[A-Z]/, 'Must contain an uppercase letter')
.regex(/[0-9]/, 'Must contain a number');
Form Persistence Config
useFormPersist({
key: 'checkout-form',
debounceMs: 500, // save after 500ms of inactivity
storage: sessionStorage, // clear on tab close
exclude: ['password', 'cvv'], // never persist sensitive fields
});
Best Practices
Validate on blur, not on change —mode: 'onBlur'prevents error messages from appearing while the user is still typing
Use Zod's.transform()— Normalize data (trim strings, lowercase emails) at the schema level, not in submit handlers
Show errors inline, not in a banner — Users fix errors 40% faster when the message is next to the field
Persist multi-step form state — UseuseFormPersistso users don't lose progress on page refresh or accidental navigation
Mark required fields, not optional ones — Most fields are required; mark the exceptions with "(optional)"
Disable submit during async validation — Prevent double-submissions and race conditions withisSubmittingstate
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| Zod errors don't show on fields | zodResolver not connected to useForm | Pass resolver: zodResolver(schema) in useForm options |
| File upload doesn't clear after submit | File input value not reset | Call reset() from useForm and clear the file input ref |
| Multi-step form loses state on back navigation | Each step unmounts and remounts | Use useFormPersist with sessionStorage to retain values |
| TypeScript errors on nested form fields | register path doesn't match schema shape | Use z.object().merge() or .extend() for nested structures |
This is 1 of 11 resources in the Frontend Developer Pro toolkit. Get the complete [Form Validation Library] with all files, templates, and documentation for $19.
Or grab the entire Frontend Developer Pro bundle (11 products) for $129 — save 30%.
SOCIAL SHARE CARD GENERATOR