--- 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 (
{fields.email.errors}
{fields.password.errors}
); } ``` ## 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 (
{fields.email.errors}
{fields.password.errors}
); } ``` ## 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
{/* Form errors */} {form.errors &&
{form.errors}
}
``` ## 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';