---
name: conform
description: Builds progressive enhancement forms with Conform using web standards, server validation, and framework integration. Use when building Remix/Next.js forms, implementing progressive enhancement, or needing accessible form validation.
---
# Conform
Type-safe form validation library with progressive enhancement for Remix and Next.js.
## Quick Start
```bash
npm install @conform-to/react @conform-to/zod zod
```
```tsx
import { useForm } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export default function LoginForm() {
const [form, fields] = useForm({
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
});
return (
);
}
```
## Remix Integration
### Action & Loader
```tsx
import { json, redirect } from '@remix-run/node';
import { useActionData } from '@remix-run/react';
import { useForm } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'At least 8 characters'),
});
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });
if (submission.status !== 'success') {
return json(submission.reply());
}
// Process valid data
await createUser(submission.value);
return redirect('/dashboard');
}
export default function SignUp() {
const lastResult = useActionData();
const [form, fields] = useForm({
lastResult,
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
shouldValidate: 'onBlur',
shouldRevalidate: 'onInput',
});
return (
);
}
```
## Next.js Integration (App Router)
### Server Action
```tsx
'use server';
import { parseWithZod } from '@conform-to/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export async function login(prevState: unknown, formData: FormData) {
const submission = parseWithZod(formData, { schema });
if (submission.status !== 'success') {
return submission.reply();
}
// Authenticate user
const user = await authenticate(submission.value);
if (!user) {
return submission.reply({
formErrors: ['Invalid credentials'],
});
}
redirect('/dashboard');
}
```
### Client Component
```tsx
'use client';
import { useFormState } from 'react-dom';
import { useForm } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { login } from './actions';
export function LoginForm() {
const [lastResult, action] = useFormState(login, undefined);
const [form, fields] = useForm({
lastResult,
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
});
return (
);
}
```
## Form Configuration
### useForm Options
```tsx
const [form, fields] = useForm({
// Last server result
lastResult,
// Client-side validation
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
// When to validate
shouldValidate: 'onBlur', // 'onSubmit' | 'onBlur' | 'onInput'
shouldRevalidate: 'onInput', // When to re-validate after error
// Initial values
defaultValue: {
email: '',
password: '',
},
// Constraint validation (HTML5)
constraint: getZodConstraint(schema),
});
```
### Form Props
```tsx
```
## Field Helpers
### getInputProps
```tsx
import { getInputProps } from '@conform-to/react';
// Generates:
// name, id, defaultValue, aria-invalid, aria-describedby
```
### getTextareaProps
```tsx
import { getTextareaProps } from '@conform-to/react';
```
### getSelectProps
```tsx
import { getSelectProps } from '@conform-to/react';
Select country
United States
```
### Checkbox/Radio
```tsx
import { getInputProps } from '@conform-to/react';
```
## Nested Objects
```tsx
const schema = z.object({
user: z.object({
name: z.string(),
email: z.string().email(),
}),
address: z.object({
street: z.string(),
city: z.string(),
}),
});
function Form() {
const [form, fields] = useForm({
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
});
const user = fields.user.getFieldset();
const address = fields.address.getFieldset();
return (
);
}
```
## Field Arrays
```tsx
import { useForm, useFieldList, insert, remove } from '@conform-to/react';
const schema = z.object({
tasks: z.array(z.object({
title: z.string(),
done: z.boolean().default(false),
})),
});
function TaskForm() {
const [form, fields] = useForm({
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
defaultValue: {
tasks: [{ title: '', done: false }],
},
});
const tasks = fields.tasks.getFieldList();
return (
);
}
```
## Intent Buttons
Handle different submit actions:
```tsx
```
```tsx
// In action
export async function action({ request }) {
const formData = await request.formData();
const intent = formData.get('intent');
if (intent === 'save') {
// Save draft
} else if (intent === 'publish') {
// Publish
}
}
```
## File Uploads
```tsx
const schema = z.object({
avatar: z.instanceof(File)
.refine((file) => file.size < 5_000_000, 'Max 5MB'),
});
function AvatarForm() {
const [form, fields] = useForm({
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
});
return (
);
}
```
## Accessibility
Conform automatically handles accessibility:
```tsx
// Using getInputProps with ariaAttributes
// Generates:
// aria-invalid="true" when has errors
// aria-describedby pointing to error element
// Error element
{fields.email.errors}
```
## Validation Schemas
### With Zod
```tsx
import { parseWithZod, getZodConstraint } from '@conform-to/zod';
const [form, fields] = useForm({
constraint: getZodConstraint(schema),
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
});
```
### With Yup
```tsx
import { parseWithYup, getYupConstraint } from '@conform-to/yup';
const [form, fields] = useForm({
constraint: getYupConstraint(schema),
onValidate({ formData }) {
return parseWithYup(formData, { schema });
},
});
```
### With Valibot
```tsx
import { parseWithValibot, getValibotConstraint } from '@conform-to/valibot';
const [form, fields] = useForm({
constraint: getValibotConstraint(schema),
onValidate({ formData }) {
return parseWithValibot(formData, { schema });
},
});
```
## Error Handling
### Field Errors
```tsx
{fields.email.errors?.map((error, i) => (
{error}
))}
```
### Form Errors
```tsx
{form.errors?.map((error, i) => (
{error}
))}
```
### Custom Server Errors
```tsx
// In action
return submission.reply({
fieldErrors: {
email: ['Email already registered'],
},
formErrors: ['Something went wrong'],
});
```
See [references/api.md](references/api.md) for complete API reference.