← Back to Docs
Recipe

Form library patterns

Composable form primitives that scale from a single input to multi-step wizards without losing type safety or validation coherence.

Core principle

Treat every form as a finite state machine. The library exposes a single useForm hook that accepts a Zod schema and returns a typed controller — values, errors, touched state, and submission status all derive from that schema.

Field composition

Fields are headless components. Each field registers itself with the nearest form context via a name string that matches a key in the Zod schema. No wrapper divs, no opinionated markup — just a render prop that receives value, onChange, and error.

Validation layering

  • 1.Zod schema defines the shape and synchronous rules.
  • 2.Async refinements (username availability, slug uniqueness) run debounced on blur.
  • 3.Server-side errors are merged back into the field error map after a failed submit.

Multi-step wizard

Split the schema into partial schemas per step. The form controller tracks the current step index and validates only the active subset. On step advance, validated fields are frozen. On final submit, the full schema runs once more before the network call.

TypeScript payoff

Because the Zod schema is the single source of truth, z.infer gives you end-to-end types from the form values through the API handler to the database insert — no manual interfaces to keep in sync.