Type-safe form validation utilizing web fundamentals using Conform and Zod (or Valibot).
Seamless form submission experience out of the box
Clean, declarative API
Type-safety powered by Zod
Single validation source for client & server using Zod
Built-in accessibility and keyboard navigation
Field auto-focus on validation error
Optimized for React 19 / Next 15 and Server Components
Prevents automatic form reset after submission
Zod error messages animated with Motion
Progressive enhancement–first APIs
The usage consists of the following steps:
useForm hook provided by Pure UI or ConformForm component*Field components or manually handle form fieldsparseWithZod in the server actionThe useForm hook is a React hook that integrates Zod schema validation with form handling using the Conform library.
It’s designed for use with RSC, specifically leveraging useActionState. It accepts a Zod schema, an optional submission result, and an action. The hook ensures validation happens on blur and revalidation occurs on input, preventing the default form reset behavior while keeping the last submission result in sync.
Essentially, it's a wrapper around the Conform useForm hook, which removes some boilerplate and provides a more convenient API and sensible defaults. You can use Conform hook instead if you prefer.
The hook returns a tuple containing form and fields metadata that you can use to enhance a HTML form by using Form and *Field components with the provided metadata.
Uses Conform’s getFormProps and sets all props required to make a form element accessible. To function properly, pass the form metadata returned by useForm hook as the validate prop.
To enhance the developer experience, additional form field components are provided that wrap inputs, adding styling, error messages, labels, and Conform’s meta binding.
To ensure proper functionality, pass the appropriate field from the fields metadata returned by the useForm hook as the validate prop.
| Prop | Type | Default |
|---|---|---|
size | enum | "default" |
radius | enum | ― |
invalid | boolean | ― |
reduceMotion | boolean | false |
validate | FieldMetadata<string | boolean | undefined> | ― |
label | ReactNode | string | ― |
| Prop | Type | Default |
|---|---|---|
type | enum | "text" |
size | enum | "default" |
radius | enum | ― |
invalid | boolean | ― |
reduceMotion | boolean | false |
validate | FieldMetadata<string | boolean | undefined> | ― |
label | ReactNode | string | ― |
| Prop | Type | Default |
|---|---|---|
size | enum | "default" |
radius | enum | ― |
invalid | boolean | ― |
reduceMotion | boolean | false |
minRows | number | 2 |
maxRows | number | ― |
resize | enum | ― |
validate | FieldMetadata<string | boolean | undefined> | ― |
label | ReactNode | string | ― |
| Prop | Type | Default |
|---|---|---|
size | enum | "default" |
invalid | boolean | ― |
reduceMotion | boolean | false |
options* | RadioOption[] | ― |
validate | FieldMetadata<string | boolean | undefined> | ― |
label | ReactNode | string | ― |
| Prop | Type | Default |
|---|---|---|
options* | OptionDataProps[] | ― |
groupedOptions* | GroupedOptionDataProps[] | ― |
size | enum | "default" |
radius | enum | ― |
reduceMotion | boolean | false |
indicator | ReactNode | ― |
animation | AnimationProps | ― |
animationPreset | enum | ― |
transition | Transition | ― |
transitionPreset | enumTransitionPreset | ― |
placeholder | ReactNode | ― |
icon | ReactNode | ― |
invalid | boolean | ― |
validate | FieldMetadata<string | boolean | undefined> | ― |
label | ReactNode | string | ― |
| Prop | Type | Default |
|---|---|---|
size | enum | "default" |
invalid | boolean | ― |
elastic | boolean | ― |
reduceMotion | boolean | false |
validate | FieldMetadata<string | boolean | undefined> | ― |
label | ReactNode | string | ― |
| Prop | Type | Default |
|---|---|---|
validate | FieldMetadata<string | boolean | undefined> | ― |
size | enum | "default" |
label | ReactNode | string | ― |
You’re not limited to using only *Field components. If you need more control over component rendering, you can use any input component with validation:
You can even use native HTML inputs:
If you don’t need Label, FormField, or error messages, you can use form inputs wrapped with Conform directly.
| Prop | Type | Default |
|---|---|---|
size | enum | "default" |
radius | enum | ― |
invalid | boolean | ― |
reduceMotion | boolean | false |
validate | FieldMetadata<string | boolean | undefined> | ― |
| Prop | Type | Default |
|---|---|---|
type | enum | "text" |
size | enum | "default" |
radius | enum | ― |
invalid | boolean | ― |
reduceMotion | boolean | false |
validate | FieldMetadata<string | boolean | undefined> | ― |
| Prop | Type | Default |
|---|---|---|
size | enum | "default" |
invalid | boolean | ― |
reduceMotion | boolean | false |
options* | RadioOption[] | ― |
validate | FieldMetadata<string | boolean | undefined> | ― |
| Prop | Type | Default |
|---|---|---|
options* | OptionDataProps[] | ― |
groupedOptions* | GroupedOptionDataProps[] | ― |
size | enum | "default" |
radius | enum | ― |
reduceMotion | boolean | false |
indicator | ReactNode | ― |
animation | AnimationProps | ― |
animationPreset | enum | ― |
transition | Transition | ― |
transitionPreset | enumTransitionPreset | ― |
placeholder | ReactNode | ― |
icon | ReactNode | ― |
invalid | boolean | ― |
validate | FieldMetadata<string | boolean | undefined> | ― |
| Prop | Type | Default |
|---|---|---|
size | enum | "default" |
invalid | boolean | ― |
elastic | boolean | ― |
reduceMotion | boolean | false |
validate | FieldMetadata<string | boolean | undefined> | ― |
| Prop | Type | Default |
|---|---|---|
size | enum | "default" |
radius | enum | ― |
invalid | boolean | ― |
reduceMotion | boolean | false |
minRows | number | 2 |
maxRows | number | ― |
resize | enum | ― |
validate | FieldMetadata<string | boolean | undefined> | ― |
| Prop | Type | Default |
|---|---|---|
validate* | FormMetadata<input<T>, string[]> | ― |
invalidAnimation | enum | ― |
| Prop | Type | Default |
|---|---|---|
legend | ReactNode | string | ― |
messages | string[] | ― |
size | enum | "default" |
| Prop | Type | Default |
|---|---|---|
messages | string[] | ― |
size | enum | "default" |
| Prop | Type | Default |
|---|---|---|
messages | string[] | ― |
size | enum | "default" |