Getting Started
Your First Form
A complete signup form using the CLI-installed components and the real adapters.
This page assumes you've run Project Setup and have
text-field, number-field, select-field, and checkbox-field installed.
Build the registry
import { defineRegistry } from '@prismrepo/forms';
import { TextField } from './text-field';
import { NumberField } from './number-field';
import { SelectField } from './select-field';
import { CheckboxField } from './checkbox-field';
export const registry = defineRegistry({
text: TextField,
number: NumberField,
select: SelectField,
checkbox: CheckboxField,
});This is the same file prismrepo add updates for you automatically as you install more field
components.
Define and render the form
import { z } from 'zod';
import { createFormBuilder } from '@prismrepo/forms';
import { usePrismForm, PrismField } from '@prismrepo/adapter-rhf';
import { createZodResolver } from '@prismrepo/adapter-zod';
import { registry } from '@/components/prism/fields/registry';
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'),
plan: z.enum(['free', 'pro', 'enterprise']),
acceptTerms: z.boolean().refine((v) => v === true, 'You must accept the terms'),
});
type SignupValues = z.infer<typeof schema>;
const definition = createFormBuilder(registry).defineForm<SignupValues>({
name: { type: 'text', label: 'Full name' },
email: { type: 'text', label: 'Email' },
age: { type: 'number', label: 'Age' },
plan: { type: 'select', label: 'Plan', options: ['free', 'pro', 'enterprise'] },
acceptTerms: { type: 'checkbox', label: 'I accept the terms' },
});
export function SignupForm() {
const form = usePrismForm(definition, {
defaultValues: {
name: '',
email: '',
age: 18,
plan: 'free',
acceptTerms: false,
},
resolver: createZodResolver(schema),
});
return (
<form
onSubmit={form.handleSubmit((values) => {
// values is fully typed as SignupValues here.
console.log(values);
})}
>
<PrismField form={form} name="name" />
<PrismField form={form} name="email" />
<PrismField form={form} name="age" />
<PrismField form={form} name="plan" />
<PrismField form={form} name="acceptTerms" />
<button type="submit" disabled={form.formState.isSubmitting}>
Create account
</button>
</form>
);
}Submitting with an empty name, an invalid email, an age under 18, or an unchecked terms box all
block submission and surface their message through the field component's own field.error,
there's no separate error channel to wire up.
What's actually happening
createFormBuilder(registry).defineForm<SignupValues>(...)checks, at compile time, that every field'stypeexists in the registry and that its component's declared value type exactly matches the corresponding key ofSignupValues. Configuringageagainst atext-typed component would fail to compile,ageis anumber.usePrismFormreturns the real, complete React Hook FormUseFormReturn<SignupValues>, plusregistryandfieldsConfig. Every RHF method,watch,setValue,reset,getValues, and the rest, is still there.createZodResolver(schema)plugs directly into RHF's ownoptions.resolver, the Zod schema's inferred type becomes the form's value type with no hand-written generic.
See Forms for the full field-configuration model, and Custom Fields to register a field component that isn't in Prism's own catalog.