Get Started
Introduction
Components
Accordion
Badge
Button
Card
Checkbox
Command
Dialog
Dropdown Menu
Input
Select
Switch
Table
Tabs
Toast
Tooltip
Forms
Introduction
Validation
Field Arrays

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>