TL;DR
react-hook-form is still the safest default for most React forms. It has the largest ecosystem, a small mental model around register, mature resolver support, and a performance profile that works well for everyday dashboards, settings screens, and multi-field product forms. TanStack Form v1 is the strongest pick when type-safe field state, custom validation timing, async validation, and framework portability matter more than terse code. Conform is the best fit when your form is really a Next.js or Remix server workflow: progressive enhancement, native FormData, and server-returned field errors are the core design rather than an integration afterthought.
The 2026 choice is less about raw speed and more about data flow. Pick react-hook-form for client-heavy React apps, TanStack Form for deeply typed interactive forms, and Conform for Server Actions or action/loader frameworks where the server should remain the source of truth.
Key Takeaways
- react-hook-form: npm latest
7.76.0; about 49.1M downloads in the 2026-05-09 to 2026-05-15 npm window; best default for broad React teams. - @tanstack/react-form: npm latest
1.32.0; about 1.9M downloads in the same npm window; best when typed field state, validation timing, and async validation are central requirements. - @conform-to/react: npm latest
1.19.2; about 153K downloads in the same npm window; best when HTML forms, Server Actions, Remix actions, and progressive enhancement are the architecture. - Server Actions are the dividing line. Next.js documents Server Actions as server functions invoked from forms with
FormData; Conform maps directly to that model, while react-hook-form and TanStack Form can integrate but usually add client-side orchestration. - Avoid benchmark absolutism. All three can be fast enough. The practical differences are render subscriptions, validation timing, server round trips, and how much boilerplate your team accepts.
2026 Decision Matrix
| App requirement | Best pick | Why |
|---|---|---|
| Most product forms, admin screens, settings pages | react-hook-form | Mature ecosystem, register API, resolver support, and low render churn with uncontrolled inputs. |
| Large typed forms with nested values and custom validation timing | TanStack Form | Field APIs expose typed state, synchronous and asynchronous validators, and explicit subscriptions. |
| Next.js App Router forms built around Server Actions | Conform | Uses native form submission, FormData, parseWithZod, and server-returned field errors. |
| Remix action forms | Conform | Its action-oriented model aligns with Remix's form/action workflow. |
| Framework-agnostic form model across React and other UI frameworks | TanStack Form | TanStack Form has a framework-agnostic core plus framework adapters. |
| Quick integration with shadcn/ui examples | react-hook-form | Most UI-kit docs and snippets still default to react-hook-form plus a schema resolver. |
| Heavy file-upload forms | Conform | Native FormData keeps files in the browser/server submission format instead of converting typed objects back into form data. |
Source and Methodology Notes
This refresh checked the official docs and package metadata on 2026-05-16:
- TanStack Form official docs returned
200athttps://tanstack.com/form/latest,https://tanstack.com/form/latest/docs/framework/react, and the React validation docs. The automated preflight timeout was a network timing issue, not evidence that the docs disappeared. - react-hook-form official docs returned
200athttps://react-hook-form.comandhttps://react-hook-form.com/get-started. - Conform official docs returned
200athttps://conform.guideandhttps://conform.guide/integration/nextjsin browser validation. - Next.js forms docs were checked for current Server Actions guidance: forms invoke server functions with the
actionattribute and receiveFormData; validation errors can be surfaced with ReactuseActionState. - npm registry metadata and downloads were checked for the 2026-05-09 to 2026-05-15 window. Treat those download numbers as directional adoption signals, not permanent rankings.
react-hook-form: Best Default for Client-Heavy React Forms
react-hook-form is the pragmatic baseline because it solves the common React form problems with little ceremony: register inputs, validate with built-in rules or a schema resolver, subscribe to form state only where needed, and submit a typed object.
The key architectural choice is uncontrolled inputs. Instead of pushing every keystroke through React state, react-hook-form registers DOM inputs and updates React subscribers when validation or watched state changes. That is why it remains comfortable for large forms, even though you should still profile real production forms before making performance claims.
Basic Zod Form
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
const alertSchema = z.object({
packageName: z.string().min(1, "Choose a package"),
threshold: z.coerce.number().min(1).max(100),
email: z.string().email("Use a valid email"),
alertType: z.enum(["downloads_drop", "version_update", "security"]),
})
type AlertFormData = z.infer<typeof alertSchema>
export function CreateAlertForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting, isDirty },
setError,
reset,
} = useForm<AlertFormData>({
resolver: zodResolver(alertSchema),
defaultValues: {
alertType: "downloads_drop",
threshold: 10,
},
mode: "onSubmit",
reValidateMode: "onChange",
})
const onSubmit = handleSubmit(async (values) => {
const result = await createAlert(values)
if (!result.ok) {
setError("packageName", {
type: "server",
message: result.message ?? "Package not found",
})
return
}
reset()
})
return (
<form onSubmit={onSubmit} noValidate>
<label htmlFor="packageName">Package name</label>
<input id="packageName" {...register("packageName")} />
{errors.packageName ? <p role="alert">{errors.packageName.message}</p> : null}
<label htmlFor="threshold">Drop threshold</label>
<input id="threshold" type="number" {...register("threshold")} />
{errors.threshold ? <p role="alert">{errors.threshold.message}</p> : null}
<label htmlFor="email">Alert email</label>
<input id="email" type="email" {...register("email")} />
{errors.email ? <p role="alert">{errors.email.message}</p> : null}
<select {...register("alertType")}>
<option value="downloads_drop">Downloads drop</option>
<option value="version_update">Version update</option>
<option value="security">Security advisory</option>
</select>
<button type="submit" disabled={isSubmitting || !isDirty}>
{isSubmitting ? "Creating…" : "Create alert"}
</button>
</form>
)
}
Where react-hook-form Wins
- Ecosystem maturity: examples, UI kit recipes, resolver packages, tutorials, and team familiarity are all strongest here.
- Client-side form ergonomics:
register,handleSubmit,Controller,watch,trigger, andsetErrorcover the common cases without forcing a custom form architecture. - Schema validation: the resolver ecosystem lets you use Zod, Valibot, Yup, Ajv, Joi, ArkType, and other validators.
- Performance-sensitive forms: uncontrolled inputs avoid the naive "React state update per keypress" model, though validation mode and watched state can still cause renders.
Where react-hook-form Needs Extra Care
- Server Actions: you can call a server action from a submit handler, but that is not the same as an HTML form whose
actionpoints directly to a server function. You need to decide how to map server errors back intosetError,reset, or optimistic UI. - File uploads:
register("file")gives you aFileList, but object-style submit handlers often require rebuildingFormDatabefore sending files to the server. - Conditional fields: unregistered fields are not part of form state. That is usually desirable, but multi-step forms need deliberate
shouldUnregister, hidden inputs, or server-side validation strategy.
TanStack Form v1: Best for Typed Field State and Validation Control
TanStack Form is now a v1 TanStack library rather than a speculative alpha. The official docs present it as a headless, framework-agnostic form library with first-class TypeScript support, field-level APIs, form-level APIs, and highly customizable validation.
The tradeoff is verbosity. TanStack Form asks you to model fields explicitly with form.Field and to decide when each validator runs. That is more code than register, but it gives you clearer control over nested values, async checks, form-level errors, and subscriptions.
Field-Level Validation Example
import { useForm } from "@tanstack/react-form"
import { z } from "zod"
const packageNameSchema = z
.string()
.min(1, "Choose a package")
.regex(/^[a-z0-9._~@/-]+$/i, "Use a valid npm package name")
export function CreateAlertForm() {
const form = useForm({
defaultValues: {
packageName: "",
threshold: 10,
email: "",
},
onSubmit: async ({ value }) => {
await createAlert(value)
},
})
return (
<form
onSubmit={(event) => {
event.preventDefault()
event.stopPropagation()
form.handleSubmit()
}}
>
<form.Field
name="packageName"
validators={{
onBlur: ({ value }) => {
const result = packageNameSchema.safeParse(value)
return result.success ? undefined : result.error.issues[0]?.message
},
onChangeAsyncDebounceMs: 500,
onChangeAsync: async ({ value }) => {
if (!value) return undefined
const exists = await checkPackageExists(value)
return exists ? undefined : "Package not found"
},
}}
>
{(field) => (
<div>
<label htmlFor={field.name}>Package name</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(event) => field.handleChange(event.target.value)}
/>
{field.state.meta.errors.map((error) => (
<p key={String(error)} role="alert">{String(error)}</p>
))}
</div>
)}
</form.Field>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit || isSubmitting}>
{isSubmitting ? "Creating…" : "Create alert"}
</button>
)}
</form.Subscribe>
</form>
)
}
Where TanStack Form Wins
- Validation timing: the official docs emphasize that validation can happen on change, input, blur, submit, and asynchronously. That makes it a natural fit for expensive checks like package-name availability.
- Typed field state: field values, metadata, and error maps are exposed directly instead of hidden behind a registration spread.
- Complex values: arrays, nested objects, linked fields, and form composition are first-class documentation topics.
- TanStack ecosystem fit: teams already using TanStack Query, Router, Table, and Start often prefer a form library with similar explicit APIs.
- Framework portability: the form core and adapter model make it attractive for teams spanning React and other frontend frameworks.
Where TanStack Form Needs Extra Care
- Boilerplate: simple login forms can look heavy compared with react-hook-form.
- Server workflows: TanStack Form has examples for framework/server integrations, but you still need to be deliberate about progressive enhancement if a plain HTML form should work before JavaScript loads.
- Team familiarity: fewer developers have production muscle memory with TanStack Form than with react-hook-form.
Conform: Best for Server Actions and Progressive Enhancement
Conform is the most opinionated of the three: HTML forms and server-side validation are the center of the design. Its Next.js integration shows a schema, a server action using parseWithZod, and a client form that receives lastResult via React useActionState.
That model matters because React and Next.js forms increasingly run through server functions. Next.js documents that a form action can invoke a server function and receive a native FormData object. Conform embraces that shape instead of converting everything into a client-side object first.
Next.js Server Action Example
// app/create-alert/schema.ts
import { z } from "zod"
export const alertSchema = z.object({
packageName: z.string().min(1, "Choose a package"),
threshold: z.coerce.number().min(1).max(100),
email: z.string().email("Use a valid email"),
})
// app/create-alert/actions.ts
"use server"
import { parseWithZod } from "@conform-to/zod"
import { redirect } from "next/navigation"
import { alertSchema } from "./schema"
export async function createAlertAction(_prevState: unknown, formData: FormData) {
const submission = parseWithZod(formData, { schema: alertSchema })
if (submission.status !== "success") {
return submission.reply()
}
await createAlert(submission.value)
redirect("/alerts")
}
// app/create-alert/page.tsx
"use client"
import { useActionState } from "react"
import { useForm, getFormProps, getInputProps } from "@conform-to/react"
import { parseWithZod } from "@conform-to/zod"
import { createAlertAction } from "./actions"
import { alertSchema } from "./schema"
export default function CreateAlertPage() {
const [lastResult, action, isPending] = useActionState(createAlertAction, undefined)
const [form, fields] = useForm({
lastResult,
onValidate({ formData }) {
return parseWithZod(formData, { schema: alertSchema })
},
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
})
return (
<form {...getFormProps(form)} action={action}>
<label htmlFor={fields.packageName.id}>Package name</label>
<input {...getInputProps(fields.packageName, { type: "text" })} />
<div id={fields.packageName.errorId}>{fields.packageName.errors}</div>
<label htmlFor={fields.threshold.id}>Drop threshold</label>
<input {...getInputProps(fields.threshold, { type: "number" })} />
<div id={fields.threshold.errorId}>{fields.threshold.errors}</div>
<label htmlFor={fields.email.id}>Alert email</label>
<input {...getInputProps(fields.email, { type: "email" })} />
<div id={fields.email.errorId}>{fields.email.errors}</div>
<button type="submit" disabled={isPending}>Create alert</button>
</form>
)
}
Where Conform Wins
- Progressive enhancement: a form can remain an HTML form with a real
action, then enhance client-side validation when JavaScript is available. - Server truth: validation happens against the same schema on the server action path that actually writes data.
- File uploads: native
FormDatais a better fit for files than object-based submit handlers. - Next.js and Remix ergonomics: action results, field metadata, and error IDs are structured around the framework submission model.
- Accessibility: generated form and input props make it easier to keep labels, descriptions, and error regions associated.
Where Conform Needs Extra Care
- Client-only apps: if your form never posts to a server action or action route, Conform's server-first model may feel like extra ceremony.
- UI-kit wrappers: native attributes work well, but some complex controlled components still need adapters.
- Team familiarity: fewer examples exist than for react-hook-form, and the mental model is different from typical client-state forms.
Feature Comparison
| Feature | react-hook-form | TanStack Form v1 | Conform |
|---|---|---|---|
| Primary model | Uncontrolled registered inputs | Headless typed field/form APIs | Native HTML form + server result metadata |
| Best fit | Client-heavy React apps | Typed interactive forms | Server Actions and Remix actions |
| Latest npm version checked | 7.76.0 | 1.32.0 | 1.19.2 for @conform-to/react |
| Last-week npm downloads checked | ~49.1M | ~1.9M | ~153K for @conform-to/react |
| Zod support | @hookform/resolvers/zod | Standard Schema / validator callbacks | @conform-to/zod |
| Async validation | Supported, often manual per field | First-class validator timing including async validators | Usually server-action validation; client validation can revalidate |
| Server Actions | Possible, but you map action errors yourself | Possible, with framework examples and manual architecture choices | Core use case in Next.js integration |
| Progressive enhancement | Possible through native/browser features, not the default mental model | Requires deliberate architecture | Core design goal |
| File uploads | Works, but object-submit flows need care | Possible, but not the primary differentiator | Natural because FormData stays native |
| Learning curve | Lowest for most React teams | Medium to high | Medium if your team knows action-based forms |
| Ecosystem maturity | Highest | Growing quickly after v1 | Focused and framework-specific |
How to Choose
Choose react-hook-form if you want the safe default
Use react-hook-form when you are building ordinary React product forms: account settings, admin forms, package filters, dashboards, and modal workflows. It has the most examples, the broadest third-party component coverage, and enough escape hatches for server errors, async validation, and custom UI controls.
The default pattern is still react-hook-form plus Zod or Valibot resolver, with Controller only for custom controlled components.
Choose TanStack Form if the form itself is complex state
Use TanStack Form when your form has enough logic that explicit field state is a benefit instead of a cost: nested arrays, dependent fields, debounced availability checks, wizard steps, form composition, typed error maps, or multiple validation timings on the same field.
It is not just a "newer react-hook-form." It is a different interface: more explicit, more typed, and often more verbose.
Choose Conform if the server owns the form
Use Conform when the form should still submit correctly as HTML, the server action is the source of truth, and the browser should receive field errors from that server response. That is especially compelling for sign-up, checkout, contact, upload, and public submission flows in Next.js App Router or Remix.
If your application already treats every form as a client-side state object, Conform can feel unfamiliar. If your application treats forms as HTTP submissions with progressive enhancement, Conform feels natural.
Practical Migration Notes
Moving from react-hook-form to TanStack Form
Do this only when you need TanStack's explicit field state. The migration is not a mechanical rename:
- Replace
register("field")spreads withform.Field name="field"render props. - Move resolver-level validation into field-level or form-level validators.
- Decide which validations run on change, blur, submit, or async debounce.
- Rebuild UI-kit wrappers around
field.state.value,field.handleChange, andfield.state.meta. - Profile only after matching validation behavior; otherwise you are comparing different forms.
Moving from react-hook-form to Conform
Do this when the form is becoming a Server Action workflow:
- Move the schema next to the server action.
- Parse the incoming
FormDataon the server withparseWithZodor another supported parser. - Return structured field errors instead of throwing for expected validation failures.
- Wire the client form with
useActionState,lastResult, and Conform's prop helpers. - Keep custom controlled widgets behind small adapters so the core form stays HTML-native.
Moving from Conform to react-hook-form
Do this when the server-action architecture is unnecessary and the form is mostly client-side interaction. Keep the server validation as a final safety gate, but let react-hook-form manage local state and schema resolver feedback.
Bottom Line
If you are searching for TanStack Form vs React Hook Form in 2026, the honest answer is not that one replaced the other. react-hook-form remains the default adoption and ergonomics winner. TanStack Form v1 is the strongest typed-state and validation-control option. Conform is the server-action-native option for teams leaning into Next.js and Remix form submissions.
For a broader non-comparison survey, see Best React Form Libraries 2026. For schema-library tradeoffs that affect all three choices, see Zod vs Yup vs Valibot 2026 and Zod v4 vs ArkType vs TypeBox vs Valibot.
