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 | enum TransitionPreset | ― |
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 | enum TransitionPreset | ― |
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" |