Best React Form Libraries in 2026
TL;DR
React Hook Form is the clear winner in 2026. React Hook Form (~12M weekly downloads) has largely displaced Formik with its uncontrolled input approach (better performance) and excellent TypeScript integration. Formik (~4M downloads) still works but has stagnated in development. TanStack Form is newer and interesting but not yet mainstream.
Key Takeaways
- React Hook Form: ~12M weekly downloads — dominant library
- Formik: ~4M downloads — declining, maintenance mode
- TanStack Form: ~500K downloads — newer, framework-agnostic
- React Hook Form + Zod — the standard combination in 2026
- Performance difference — RHF uses uncontrolled inputs; Formik re-renders on every keystroke
React Hook Form (Recommended)
React Hook Form's fundamental advantage is architectural: it uses uncontrolled inputs. Where Formik stores all field values in React state and re-renders the entire form on every keystroke, React Hook Form registers inputs via a ref and reads their values directly from the DOM when needed (on submit, on validate). This approach eliminates the per-keystroke re-renders that make large Formik forms feel sluggish.
The practical difference becomes noticeable on forms with many fields — a 20-field registration form in Formik triggers 20+ state updates and renders per field while the user types. React Hook Form's version triggers zero re-renders during typing (unless validation is configured to run onChange). For forms in performance-sensitive contexts (modals, complex wizards, mobile web apps), this matters.
The zodResolver from @hookform/resolvers/zod is the standard pairing. You define your form schema with Zod — getting TypeScript types, runtime validation, and good error messages from a single source — and pass the resolver to useForm. The FormData type is derived automatically with z.infer<typeof schema>, ensuring your form values and your schema never get out of sync.
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email'),
age: z.number().min(18, 'Must be 18 or older'),
terms: z.literal(true, { errorMap: () => ({ message: 'Must accept terms' }) }),
});
type FormData = z.infer<typeof schema>;
function SignupForm() {
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({
resolver: zodResolver(schema),
});
const onSubmit = async (data: FormData) => {
await createAccount(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}
<input {...register('email')} type="email" />
{errors.email && <span>{errors.email.message}</span>}
<input {...register('age', { valueAsNumber: true })} type="number" />
{errors.age && <span>{errors.age.message}</span>}
<input {...register('terms')} type="checkbox" />
{errors.terms && <span>{errors.terms.message}</span>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Creating account...' : 'Sign up'}
</button>
</form>
);
}
Performance: Uncontrolled inputs — no re-renders during typing until validation triggers.
Formik (Legacy, Still Works)
Formik was the dominant React form library from 2018 to 2021, and its developer experience innovations (the <Field> component, <Form>, <ErrorMessage>, the onSubmit with actions) were genuinely influential. Many React developers learned form handling through Formik, and the library's approach of "this is just a controlled React form with some helpers" made it approachable.
The performance problem is the controlled input model. Formik stores form state in React state, which means every keystroke triggers a state update and a re-render of the form component and all its children. For small forms (2-5 fields), this is imperceptible. For complex forms — multi-step wizards, data-entry forms with 20+ fields, forms with expensive validation — the re-render overhead becomes noticeable lag.
Formik's maintenance status is the other concern. The library has not had a major release since 2021, and its GitHub issues queue has hundreds of open items. It is not abandoned, but it is not actively evolving either. New React patterns (Server Actions, concurrent features) are not being added to Formik.
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as yup from 'yup';
const schema = yup.object({
name: yup.string().required('Name is required'),
email: yup.string().email().required(),
});
function SignupForm() {
return (
<Formik
initialValues={{ name: '', email: '' }}
validationSchema={schema}
onSubmit={async (values, actions) => {
await createAccount(values);
actions.setSubmitting(false);
}}
>
{({ isSubmitting }) => (
<Form>
<Field name="name" />
<ErrorMessage name="name" component="span" />
<Field name="email" type="email" />
<ErrorMessage name="email" component="span" />
<button type="submit" disabled={isSubmitting}>Sign up</button>
</Form>
)}
</Formik>
);
}
Performance: Controlled inputs — re-renders on every keystroke. Noticeable lag on large forms.
TanStack Form (Emerging)
TanStack Form is the most technically ambitious entry in this space. Built by the TanStack team (also responsible for TanStack Query, TanStack Router, and TanStack Table), it's framework-agnostic — the core works with React, Vue, Solid, and Angular. This is achieved through a separate adapters model, where @tanstack/react-form exports React-specific wrappers around the framework-agnostic core.
The API is more explicit than React Hook Form. Rather than registering inputs via {...register('email')}, TanStack Form uses a render-prop pattern through form.Field that gives you direct access to the field's state and handlers. This is more verbose for simple forms but provides fine-grained control over re-rendering and validation timing.
TanStack Form's async validation support is built-in and first-class — you can define both sync and async validators per field, with debouncing and cancellation handled automatically. React Hook Form requires more manual plumbing to achieve equivalent async validation behavior.
import { useForm } from '@tanstack/react-form';
import { zodValidator } from '@tanstack/zod-form-adapter';
import { z } from 'zod';
function SignupForm() {
const form = useForm({
defaultValues: { email: '', password: '' },
validatorAdapter: zodValidator(),
onSubmit: async ({ value }) => {
await createAccount(value);
},
});
return (
<form onSubmit={e => { e.preventDefault(); form.handleSubmit(); }}>
<form.Field
name="email"
validators={{ onChange: z.string().email() }}
children={(field) => (
<>
<input
value={field.state.value}
onChange={e => field.handleChange(e.target.value)}
/>
{field.state.meta.errors.map(e => <span key={e}>{e}</span>)}
</>
)}
/>
<button type="submit">Submit</button>
</form>
);
}
Framework-agnostic. Works with React, Vue, Solid, and Angular. Not yet the ecosystem standard.
Comparison Table
| Library | Downloads | Bundle | Re-renders | TypeScript | Validation |
|---|---|---|---|---|---|
| React Hook Form | 12M | ~8KB | Minimal | Excellent | Via resolver |
| Formik | 4M | ~15KB | On every keystroke | Good | Yup built-in |
| TanStack Form | 500K | ~10KB | Minimal | Excellent | Via adapter |
Package Health
The download trajectory of these libraries tells the story of the React form ecosystem clearly. React Hook Form has grown from roughly 4M weekly downloads in 2021 to 12M in 2026 — driven by the @hookform/resolvers pattern, shadcn/ui's adoption of it as the default form library, and the community consensus that it's the performant standard.
Formik's decline from its peak of ~8M weekly downloads is significant. It still has 4M downloads per week largely from existing applications that haven't migrated and from developers who learned form handling with Formik and default to it in new projects. New project selection in 2026 heavily favors React Hook Form.
| Package | Weekly Downloads | Last Major Release | Maintenance |
|---|---|---|---|
| react-hook-form | ~12M | Active (2026) | Active |
| formik | ~4M | 2.4.x (2022) | Maintenance only |
| @tanstack/react-form | ~500K | Active (2026) | Active |
| @hookform/resolvers | ~8M | Active (2026) | Active |
The @hookform/resolvers download count exceeding React Hook Form itself indicates how standard the Zod resolver pattern has become — many developers install both together as a baseline.
Validation Integration
Validation is where form library choices intersect with schema library choices. The ecosystem has largely converged around two patterns: React Hook Form + Zod, and Formik + Yup. But there are important nuances.
React Hook Form + Zod is the dominant combination in 2026. Zod's TypeScript-first API means you define your schema once and get both runtime validation and static types. The zodResolver from @hookform/resolvers/zod bridges the two libraries:
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
// Complex validation with Zod
const checkoutSchema = z.object({
email: z.string().email(),
card: z.object({
number: z.string().regex(/^\d{16}$/, 'Card number must be 16 digits'),
expiry: z.string().regex(/^(0[1-9]|1[0-2])\/\d{2}$/, 'Format: MM/YY'),
cvv: z.string().regex(/^\d{3,4}$/, 'CVV must be 3-4 digits'),
}),
address: z.object({
line1: z.string().min(1),
city: z.string().min(1),
zip: z.string().min(5),
country: z.string().length(2, 'Use 2-letter country code'),
}),
}).refine(data => {
// Cross-field validation
return data.card.expiry > getCurrentMonthYear();
}, { message: 'Card is expired', path: ['card', 'expiry'] });
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(checkoutSchema),
});
// Nested field error access
errors.card?.number?.message; // 'Card number must be 16 digits'
errors.address?.zip?.message; // 'String must contain at least 5 character(s)'
React Hook Form + Valibot is an emerging pattern. Valibot is a newer schema library that's significantly smaller than Zod (~5KB vs ~22KB) with a similar API. For bundle-size-critical applications, the @hookform/resolvers/valibot resolver makes switching easy. See our Zod vs TypeBox comparison for schema library tradeoffs.
Inline validation vs schema validation is worth addressing: React Hook Form supports both. Schema validation (via resolvers) runs on submit and on field blur/change depending on your mode setting. Inline validation via validate functions in register is useful for field-specific async validation (like checking if an email is already registered):
<input
{...register('email', {
validate: async (value) => {
const exists = await checkEmailExists(value);
return !exists || 'This email is already registered';
},
})}
/>
Performance Deep Dive
The controlled vs uncontrolled input distinction is the most important performance consideration for React forms. Controlled inputs store their value in React state — every character typed triggers setState, which triggers a re-render. Uncontrolled inputs store their value in the DOM — React never knows about individual keystrokes.
React Hook Form's uncontrolled approach means a 20-field form has zero React renders while the user types. Formik's controlled approach means 20 fields × N characters typed = N renders per field. With React DevTools Profiler, this difference is starkly visible:
Controlled form (Formik) performance profile:
- User types "hello" in email field
- 5 state updates → 5 re-renders of the form component
- Each re-render: reconcile 20 field components
- On a slow mobile device: potential frame drops
Uncontrolled form (React Hook Form) performance profile:
- User types "hello" in email field
- 0 state updates during typing
- 0 re-renders during typing
- Validation may trigger 1 re-render if mode: 'onChange'
React Hook Form does trigger re-renders on submit (to show errors) and when validation state changes. The useFormState hook allows subscribing to specific slices of form state — if only the submit button needs to know about isSubmitting, using useFormState({ name: 'isSubmitting' }) prevents other components from re-rendering.
import { useForm, useFormState } from 'react-hook-form';
function SubmitButton({ control }) {
// Only this component re-renders when isSubmitting changes
const { isSubmitting, isValid } = useFormState({ control });
return (
<button disabled={isSubmitting || !isValid}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
);
}
Recommendations
| Scenario | Pick |
|---|---|
| New React project | React Hook Form + Zod |
| Existing Formik codebase | Keep Formik (migration cost) |
| Multi-framework support needed | TanStack Form |
| Complex wizard forms | React Hook Form (useFormContext) |
| shadcn/ui forms | React Hook Form (built into shadcn) |
Schema Validation Integration
Schema validation is now so tightly coupled to form handling that the choice of schema library is almost inseparable from the choice of form library. In 2026, Zod has won this pairing decisively for React Hook Form users. The @hookform/resolvers package ships resolvers for Zod, Yup, Valibot, Joi, and several others, but the Zod resolver is by far the most used.
The architectural shift that Zod brings is that your validation schema becomes the source of truth for both runtime validation and TypeScript types. Before Zod + React Hook Form, developers typically wrote a TypeScript type for their form data, then wrote a separate Yup schema that had to stay in sync. If you added a field to the type, you had to remember to add it to the schema — and vice versa. Zod eliminates this with z.infer<typeof schema>, which derives the TypeScript type directly from the schema definition.
Formik's default pairing is Yup, and that combination remains functionally sound for existing codebases. Yup's API is mature and the error messages are often more human-readable than Zod's defaults. The practical disadvantage is that Yup does not generate TypeScript types — you still define your form data type separately and rely on the Yup schema matching it. For greenfield TypeScript projects, that maintenance burden is a reason to prefer Zod.
Valibot is an emerging third option worth knowing about. At roughly 5KB versus Zod's 22KB, Valibot offers a nearly identical schema definition API with dramatic bundle savings. The @hookform/resolvers/valibot resolver works the same way as the Zod resolver. For browser-rendered forms where bundle size is a primary concern — landing pages, public-facing marketing forms — the Valibot option meaningfully improves your Lighthouse scores. The tradeoff is a smaller ecosystem and fewer third-party integrations.
Cross-field validation is where schema libraries differ most. Zod's .refine() and .superRefine() methods let you validate across fields — checking that a confirm-password field matches the password field, that a date range has the end date after the start date, or that a percentage field sums to 100. This logic lives in the schema rather than scattered across form event handlers, which makes it testable in isolation.
Server Actions and Forms in 2026
React 19's useActionState hook and Next.js Server Actions have changed how forms integrate with the server. The traditional pattern was: form submission → fetch/axios call to API route → update state with response. The new pattern is: form submission → Server Action runs on the server → response updates component state. The form library landscape is catching up to this shift at different speeds.
React Hook Form added useFormState (and in React 19, useActionState) integration through its controller APIs. The pattern involves defining a Server Action that validates the incoming FormData, then returning structured errors or success data. The client-side React Hook Form setup captures those server errors and maps them back to field-level error state via setError. This works, but the wiring requires boilerplate — you write both the Server Action's return format and the client-side error mapping code.
The performance implication worth understanding: Server Actions submit as native HTML FormData, not as JSON. React Hook Form's handleSubmit provides a JavaScript-intercepted submission that serializes state as a typed object. To use Server Actions with React Hook Form, you either pass the Server Action as the form action attribute directly (bypassing React Hook Form's submit handler), or you wire the action through handleSubmit using useActionState. The two approaches have different progressive enhancement properties — the direct action approach works without JavaScript; the handleSubmit approach does not.
For applications that specifically need progressive enhancement — forms that submit correctly before JavaScript loads, or in degraded network conditions — the architectural mismatch between React Hook Form's JavaScript-centric model and Server Actions' HTML-native model becomes a practical concern. This is the gap that Conform fills deliberately: Conform was built to treat Server Actions as a first-class submission target, with the HTML action attribute as the primary mechanism and JavaScript enhancement layered on top.
If you're building a Next.js 15 application with critical forms (checkout, authentication, data entry), the decision between React Hook Form and Conform should be driven by whether progressive enhancement matters for your user base. For internal dashboards and admin tools where JavaScript can be assumed, React Hook Form remains the pragmatic default. For public-facing applications, Conform's approach aligns better with the Server Actions model.
Compare form library health scores on PkgPulse. For schema validation choices, see Zod vs TypeBox 2026. Explore best React hook libraries for complementary utilities.