Comparison Overview
Feature matrix
| Features | Kin Form | React Hook Form | Formik | TanStack Form |
|---|---|---|---|---|
| Zero dependencies | ✅ | ✅ | ❌ | ⚠️ |
| Framework-agnostic core | ✅ | ❌ | ❌ | ✅ |
| Type-safe nested field paths | ✅ | ⚠️ | ❌ | ✅ |
| Standard Schema support | ⚠️ | ⚠️ | ❌ | ✅ |
| Same model for field, group, array, and form | ✅ | ❌ | ❌ | ❌ |
| Localized subscription | ✅ | ❌ | ❌ | ❌ |
| Selective re-rendering | ✅ | ⚠️ | ⚠️ | ✅ |
| Built-in async-validation debounce | ✅ | ❌ | ❌ | ✅ |
| Declarative cross-field revalidation | ✅ | ⚠️ | ❌ | ✅ |
| Field state survives list virtualization | ✅ | ⚠️ | ✅ | ✅ |
✅ full support · ⚠️ partial or conditional · ❌ not supported
API surface
Not whether a feature exists, but which APIs/hooks/types you have to learn to
use it. Only what a typical app imports by name (a reusable field component, an
onSubmit signature), not every public export:
| Kin Form | React Hook Form | Formik | TanStack Form | |
|---|---|---|---|---|
| Types | FieldApiFormApi | FieldValuesFieldPathFieldPathByValueControlUseControllerPropsRegisterOptions | FormikPropsFormikHelpersFormikErrorsFieldProps | AnyFieldApi |
| Hooks & components | useFormuseWatchWatchuseMultistep | useFormuseControllerControlleruseFieldArrayuseWatchuseFormStateuseFormContextFormProvider | useFormikFormikuseFieldFieldFieldArrayuseFormikContextErrorMessage | useFormuseAppFormuseFielduseFieldContextuseStoreform.Fieldform.AppFieldform.FormGroupform.SubscribewithFormwithFieldGroupcreateFormHookcreateFormHookContexts |
Kin Form models every node (leaf, group, array, root) with one field type, so there's one small set of APIs to learn instead of one per level.
Bundle size (React usage)
Every library above is measured directly with the same toolchain: rolldown and
its own built-in minifier, then gzip. That's not the same as Bundlephobia, which
minifies with terser by default and can produce different sizes for the same
source. Reproducible via deno task --cwd scripts bundle-size.
Performance
Smaller is better.
Initial mount (84 fields)
Flat field update burst (800x)
Nested field update burst (800x)
Array swap burst (800x, cycling adjacent pairs)
Array insert/remove burst (800x)
Flat field update burst + sync validator (800x)
Debounced async validation (800 updates, 50ms debounce)
Whole-form zod schema validation (on submit)
Untouched sibling re-renders (800 updates)
Flat field update burst (800x)
Nested field update burst (800x)
These are wall-clock numbers from one shared ~84-field form (flat fields, a
nested group, an array) in a real (Happy DOM) React tree, reproducible via
deno task --cwd scripts speed-bench.
The benchmark also counts re-renders per scenario, how many untouched sibling fields re-render when one field updates, charted above for the two scenarios where that count is clean and isolated.