DEV Community

Alex Spinov
Alex Spinov

Posted on

Conform Has a Free Type-Safe Form Library — Here's How to Use It

Server-side form validation that matches client-side validation — without duplicating logic. Conform bridges the gap with progressive enhancement and type-safe schemas.

What Is Conform?

Conform is a type-safe form validation library for React, built for progressive enhancement. It works with Remix, Next.js, and any React framework — validating on both client and server using the same schema.

Quick Start

npm install @conform-to/react @conform-to/zod zod
Enter fullscreen mode Exit fullscreen mode
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 function LoginForm() {
  const [form, fields] = useForm({
    onValidate({ formData }) {
      return parseWithZod(formData, { schema });
    },
    shouldValidate: 'onBlur',
  });

  return (
    <form id={form.id} onSubmit={form.onSubmit} noValidate>
      <div>
        <label htmlFor={fields.email.id}>Email</label>
        <input
          id={fields.email.id}
          name={fields.email.name}
          type="email"
          defaultValue={fields.email.initialValue}
        />
        <p>{fields.email.errors}</p>
      </div>

      <div>
        <label htmlFor={fields.password.id}>Password</label>
        <input
          id={fields.password.id}
          name={fields.password.name}
          type="password"
        />
        <p>{fields.password.errors}</p>
      </div>

      <button type="submit">Log in</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Server Validation (Remix)

import { parseWithZod } from '@conform-to/zod';

export async function action({ request }) {
  const formData = await request.formData();
  const submission = parseWithZod(formData, { schema });

  if (submission.status !== 'success') {
    return submission.reply();
  }

  // submission.value is typed as { email: string, password: string }
  await login(submission.value);
  return redirect('/dashboard');
}
Enter fullscreen mode Exit fullscreen mode

Same schema validates on client AND server. No duplication.

Progressive Enhancement

Conform works without JavaScript:

  1. User submits form → server validates → returns errors
  2. With JS enabled → client validates before submission
  3. Same schema, same errors, both paths

Complex Forms

Nested Objects

const schema = z.object({
  address: z.object({
    street: z.string(),
    city: z.string(),
    zip: z.string().regex(/^\d{5}$/),
  }),
});
Enter fullscreen mode Exit fullscreen mode

Dynamic Arrays

const schema = z.object({
  items: z.array(z.object({
    name: z.string(),
    quantity: z.number().min(1),
  })).min(1, 'Add at least one item'),
});

function OrderForm() {
  const [form, fields] = useForm({ /* ... */ });
  const items = fields.items.getFieldList();

  return (
    <form>
      {items.map((item, index) => {
        const itemFields = item.getFieldset();
        return (
          <fieldset key={item.key}>
            <input name={itemFields.name.name} />
            <input name={itemFields.quantity.name} type="number" />
            <button {...form.remove.getButtonProps({ name: fields.items.name, index })}>
              Remove
            </button>
          </fieldset>
        );
      })}
      <button {...form.insert.getButtonProps({ name: fields.items.name })}>
        Add Item
      </button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Why Conform

Feature Conform React Hook Form Formik
Server validation Built-in Manual Manual
Progressive enhancement Yes No No
Schema validation Zod/Yup Resolver Yup
No JS fallback Works Broken Broken
FormData native Yes Custom Custom
Remix/Next.js First-class Plugin Manual

Get Started


Building forms that process web data? My Apify scrapers extract structured data from any site. Custom solutions: spinov001@gmail.com

Top comments (0)