Form Validation
Schema-driven validation built on createForm. Cross-field rules use the validator's own combinators.
See interactive examples below.
1import { createForm } from '@barefootjs/form'2import { z } from 'zod'34const form = createForm({5 schema: z.object({6 name: z.string().min(1, 'Name is required'),7 }),8 defaultValues: { name: '' },9 validateOn: 'blur',10 revalidateOn: 'input',11})1213const name = form.field('name')1415<Input16 value={name.value()}17 onInput={name.handleInput}18 onBlur={name.handleBlur}19/>20<p>{name.error()}</p>#Overview
Validation lives in the schema you pass to createForm. Each field exposes value, error, touched, and the handleInput/handleBlur handlers — wire them to the input. For cross-field rules use the validator's combinators (e.g. Zod's .refine) and target a specific field via path.
#Examples
#Required Field
1import { createForm } from '@barefootjs/form'2import { z } from 'zod'34const form = createForm({5 schema: z.object({6 name: z.string().min(1, 'Name is required'),7 }),8 defaultValues: { name: '' },9 validateOn: 'blur',10 revalidateOn: 'input',11})1213const name = form.field('name')1415<Input16 value={name.value()}17 onInput={name.handleInput}18 onBlur={name.handleBlur}19/>20<p>{name.error()}</p>#Email Format
1const form = createForm({2 schema: z.object({3 email: z4 .string()5 .min(1, 'Email is required')6 .email('Invalid email format'),7 }),8 defaultValues: { email: '' },9 validateOn: 'blur',10 revalidateOn: 'input',11})1213const email = form.field('email')14const isValid = () => email.touched() && email.error() === ''#Cross-Field (Password Confirmation)
1// Use Zod's .refine to compare two fields. The error attaches to2// `confirmPassword` via `path`, so it shows up on `confirm.error()`.3const form = createForm({4 schema: z5 .object({6 password: z.string().min(8, 'Password must be at least 8 characters'),7 confirmPassword: z.string().min(1, 'Please confirm your password'),8 })9 .refine((d) => d.password === d.confirmPassword, {10 message: 'Passwords do not match',11 path: ['confirmPassword'],12 }),13 defaultValues: { password: '', confirmPassword: '' },14 validateOn: 'blur',15 revalidateOn: 'input',16})#Multi-Field Form
1const form = createForm({2 schema: z3 .object({4 name: z.string().min(2, 'Name must be at least 2 characters'),5 email: z.string().email('Invalid email format'),6 password: z.string().min(8, 'Password must be at least 8 characters'),7 confirmPassword: z.string().min(1, 'Please confirm your password'),8 })9 .refine((d) => d.password === d.confirmPassword, {10 message: 'Passwords do not match',11 path: ['confirmPassword'],12 }),13 defaultValues: { name: '', email: '', password: '', confirmPassword: '' },14 validateOn: 'blur',15 revalidateOn: 'input',16 onSubmit: async (data) => {17 await fetch('/api/register', { method: 'POST', body: JSON.stringify(data) })18 },19})2021<form onSubmit={form.handleSubmit}>22 {/* fields ... */}23 <Button type="submit" disabled={form.isSubmitting()}>24 {form.isSubmitting() ? 'Submitting...' : 'Submit'}25 </Button>26</form>