Build your field components once. Reuse them everywhere.
A framework-agnostic form state library for TypeScript.
Forms read like composition, not wiring.
<form onSubmit={form.handleSubmit}>
<TextField api={form.field("email")} label="Email" />
<AddressField api={form.field("shipping")} />
<AddressField api={form.field("billing")} />
<ItemsField api={form.field("items")} />
<SubmitButton api={form}>Place order</SubmitButton>
</form>;Each component receives a resolved FieldApi, not a path or form context.
Define the UI and behavior once, then mount it anywhere its value type fits. Kin
Form keeps that component independently subscribed, so a change only updates the
part of the form that depends on it. See
Form Composition for the
full pattern.
Reusable field components become awkward when a library treats the form as the only stateful object and fields as proxies into it. Nested objects, arrays, and shared validation then need their own special mechanisms.
Kin Form treats a form as a tree where every node (leaf, group, or the form
itself) is the same thing, with its own state, configuration, and subscribers.
That is why one component pattern works at every level. Dotted paths like
field("address.line1") type-check against that same tree, so a typo'd path is
a compile error, not a runtime surprise.
See kintools.dev/form for how the same value shape can be structured as different valid trees.
| Kin Form | React Hook Form | Formik | TanStack Form | |
|---|---|---|---|---|
| Zero dependencies | ✅ | ✅ | ❌ | |
| Framework-agnostic core | ✅ | ❌ | ❌ | ✅ |
| Type-safe nested field paths | ✅ | ❌ | ✅ | |
| Standard Schema support | ❌ | ✅ | ||
| Same primitive 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 ·
Each package's full public API, bundled and minified the same way (rolldown)
then gzipped. Reproduce with deno task --cwd scripts bundle-size. Not directly
comparable to Bundlephobia, which uses a different minifier (terser).
@kintools/form-core ██████░░░░░░░░░░░░░░░░░░ 4.5 KB
@kintools/form-react (bindings only) █░░░░░░░░░░░░░░░░░░░░░░░ 0.8 KB
@kintools/form-lit (bindings only) ██░░░░░░░░░░░░░░░░░░░░░░ 1.3 KB
@kintools/form-validators █░░░░░░░░░░░░░░░░░░░░░░░ 0.7 KB
Kin Form (core + react bindings) ███████░░░░░░░░░░░░░░░░░ 5.2 KB
Kin Form (core + lit bindings) ███████░░░░░░░░░░░░░░░░░ 5.5 KB
React Hook Form ██████████████████░░░░░░ 13.7 KB
Formik ██████████████████░░░░░░ 13.9 KB
TanStack Form ████████████████████████ 18.5 KB
Flat field update, 800x burst, wall-clock median in Happy DOM: 1.39 ms for
Kin Form vs 66.55 ms (React Hook Form), 3.30 ms (Formik), 564.24 ms (TanStack
Form). One scenario from a shared ~84-field benchmark form driven through the
same update plan against each library. Reproduce with
deno task --cwd scripts speed-bench.
See the full comparison for the complete scenario breakdown (nested/array updates, validation, mount cost), methodology notes, and code-by-code comparisons.
| Package | Description |
|---|---|
@kintools/form-core |
FieldApi, FormApi — the framework-agnostic form engine |
@kintools/form-react |
useForm, useWatch, useMultistep, Watch — React bindings |
@kintools/form-lit |
watch, WatchController, MultistepController — Lit bindings |
@kintools/form-validators |
required, minLength, maxLength, min, max, url, email, pattern, maxFileSize, password, toSchemaValidator() |
@kintools/form-devtools-react |
DevtoolsProvider, useFormDevtools — inspector panel for a form's live tree state during development |
skills/kin-form teaches coding agents (Claude
Code, Cursor, Codex, ...) Kin Form's patterns and pitfalls. Install it with:
npx skills add kintools-dev/formOr copy skills/kin-form/ into your project's .claude/skills/.
See CONTRIBUTING.md for project layout, running tests, and the PR process.