Skip to Content
Prism UI
Forms

Default Values and Validation

Setting default values and wiring required, custom, and schema-driven validation.

Default values

usePrismForm's second argument is passed straight through to React Hook Form's own useForm, so defaultValues works exactly as it does in RHF:

const form = usePrismForm(definition, {
  defaultValues: { name: '', email: '', age: 18, acceptTerms: false },
});

FormValues (the generic on defineForm) determines the shape defaultValues must satisfy, TypeScript rejects a missing or mistyped key the same way it would on a plain useForm call.

Required-field and native validation

PrismField accepts an optional rules prop, RHF's own RegisterOptions, passed straight through to Controller:

<PrismField form={form} name="email" rules={{ required: 'Email is required' }} />

This is the documented escape hatch for reaching RHF's native validation directly. For schema-driven validation across an entire form, see the next section, most consumers prefer a single Zod schema over per-field rules.

Schema-driven validation with Zod

@prismrepo/adapter-zod's createZodResolver adapts a Zod schema into the resolver shape usePrismForm's options.resolver accepts:

import { z } from 'zod';
import { createZodResolver } from '@prismrepo/adapter-zod';

const schema = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().min(1, 'Email is required').email('Invalid email address'),
  age: z.number().min(18, 'Must be at least 18 years old'),
});

const form = usePrismForm(definition, {
  defaultValues: { name: '', email: '', age: 18 },
  resolver: createZodResolver(schema),
});

A failed validation surfaces through the exact same field.error every field component already reads, there is no separate Zod-specific error channel. createZodResolver's input/output types are inferred from the schema itself, not a hand-written generic parameter.

Custom validation

Custom, cross-field, or conditional validation is ordinary Zod: .refine() and .superRefine() work exactly as they do with any other Zod schema, and the result still flows through the same resolver:

const schema = z
  .object({
    password: z.string().min(8, 'At least 8 characters'),
    confirmPassword: z.string(),
    acceptTerms: z.boolean(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: 'Passwords must match',
    path: ['confirmPassword'],
  })
  .refine((data) => data.acceptTerms === true, {
    message: 'You must accept the terms',
    path: ['acceptTerms'],
  });

A .refine() targeting a specific field via path surfaces its message through that field's own field.error, the same as any other validation failure.

See Combining RHF and Zod for how resolver precedence works when both rules and a Zod resolver are present on the same form, and Zod Integration for the adapter's full API and version requirements.

On this page