agents-inc/skills/src/skills/web-forms-tanstack-form/SKILL.md
web-forms-tanstack-form
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety
- Source repository stars
- 23
- Declared platforms
- 0
- Static risk flags
- 0
- Last source update
- 2026-08-09
- Source checked
- 2026-08-28
Decision brief
What it does: where it fits
Quick Guide: Use useForm with defaultValues and typed generics. Render fields with form.Field using the render-prop children pattern. Validation lives in the validators prop on both form and field level — use onChange, onBlur, onSubmit (sync) and their Async variants. Use mode="…
Not for
- Tasks that require unconfirmed production actions or broad system permissions.
- Environments where the pinned source and install steps cannot be inspected.
Compatibility matrix
Platform support, with evidence labels
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
Inspect first. Install second.
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/agents-inc/skills --skill "src/skills/web-forms-tanstack-form"Inspect the Agent Skill "web-forms-tanstack-form" from https://github.com/agents-inc/skills/blob/81d43a51211aca12c85dcc16085fa99014ec548e/src/skills/web-forms-tanstack-form/SKILL.md at commit 81d43a51211aca12c85dcc16085fa99014ec548e. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
What the source asks the agent to do
- 01
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)
Building type-safe forms where field types are inferred from defaultValuesManaging complex validation with sync, async, and cross-field rulesDynamic forms with add/remove field groups (array fields) - 02
Philosophy
TanStack Form is headless and type-safe by design. It owns zero UI — you render every input yourself. The library provides form state, validation orchestration, and field management. Types flow from defaultValues through every field name, value, and error — no manual generics re…
Type inference from defaults - defaultValues defines the form shape; field names and values are fully typedHeadless - Zero UI opinions; works with any component library or native inputsValidation-event-driven - Validators attach to specific events (onChange, onBlur, onSubmit) per field or per form - 03
Core Patterns
Every form starts with useForm and renders fields via form.Field. The children render prop receives the field API with state, handleChange, and handleBlur.
Every form starts with useForm and renders fields via form.Field. The children render prop receives the field API with state, handleChange, and handleBlur.Key difference from other form libraries: No register, no Controller, no ref forwarding. You always use field.handleChange and field.state.value explicitly.See examples/core.md for complete form with error display and accessibility. - 04
Pattern 1: Basic useForm + form.Field
Every form starts with useForm and renders fields via form.Field. The children render prop receives the field API with state, handleChange, and handleBlur.
Every form starts with useForm and renders fields via form.Field. The children render prop receives the field API with state, handleChange, and handleBlur.Key difference from other form libraries: No register, no Controller, no ref forwarding. You always use field.handleChange and field.state.value explicitly.See examples/core.md for complete form with error display and accessibility. - 05
Pattern 2: Field-Level Validation
Validators are functions on the validators prop. Sync validators return a string (error) or undefined (valid). Async validators use onChangeAsync, onBlurAsync, onSubmitAsync.
Validators are functions on the validators prop. Sync validators return a string (error) or undefined (valid). Async validators use onChangeAsync, onBlurAsync, onSubmitAsync.Sync-first gating: When both onBlur and onBlurAsync exist, the async validator only runs if the sync validator passes. Same for onChange/onChangeAsync.See examples/validation.md for all validation patterns and adapter integration.
Permission review
Static risk signals and limitations
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
Why each signal appears
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 92/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 23 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Provenance and original SKILL.md
- Repository
- agents-inc/skills
- Skill path
- src/skills/web-forms-tanstack-form/SKILL.md
- Commit
- 81d43a51211aca12c85dcc16085fa99014ec548e
- License
- MIT
- Collected
- 2026-08-28
- Default branch
- main
View the original SKILL.md
TanStack Form Patterns
Quick Guide: Use
useFormwithdefaultValuesand typed generics. Render fields withform.Fieldusing the render-propchildrenpattern. Validation lives in thevalidatorsprop on both form and field level — useonChange,onBlur,onSubmit(sync) and theirAsyncvariants. Usemode="array"for dynamic field lists withpushValue/removeValue. UseonChangeListenTofor cross-field validation. For app-wide consistency, create a shareduseAppFormviacreateFormHook. Always providedefaultValues— TanStack Form infers types from them.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST provide defaultValues to useForm — TanStack Form infers field types from them)
(You MUST use form.Field with the children render prop — TanStack Form does not use register or Controller)
(You MUST use the validators prop for validation — NOT inline rules or external resolver wrappers)
(You MUST handle field.state.meta.errors as an array — always .map() over errors)
(You MUST call form.handleSubmit() inside the form's onSubmit handler with e.preventDefault())
</critical_requirements>
Auto-detection: TanStack Form, @tanstack/react-form, @tanstack/vue-form, @tanstack/solid-form, @tanstack/angular-form, @tanstack/lit-form, useForm from tanstack, form.Field, createFormHook, createFormHookContexts, useAppForm, fieldContext, formContext, handleSubmit tanstack, pushValue, removeValue, onChangeListenTo, field.handleChange, field.handleBlur, field.state, formDevtoolsPlugin
When to use:
- Building type-safe forms where field types are inferred from
defaultValues - Managing complex validation with sync, async, and cross-field rules
- Dynamic forms with add/remove field groups (array fields)
- Multi-framework projects (React, Vue, Solid, Angular, Lit)
- Projects already using the TanStack ecosystem
When NOT to use:
- Single input without validation (use native state)
- Server-only forms with server actions (use native form + action)
- Read-only data display (not a form scenario)
Table of Contents
Detailed Resources:
- examples/core.md - Basic form, Field component, TypeScript, form submission
- examples/validation.md - Sync/async validation, validator adapters, form-level validation
- examples/arrays.md - Dynamic array fields with pushValue/removeValue
- examples/composition.md - createFormHook, useAppForm, listeners, side effects
- reference.md - API tables, validator events, decision frameworks
Philosophy
TanStack Form is headless and type-safe by design. It owns zero UI — you render every input yourself. The library provides form state, validation orchestration, and field management. Types flow from defaultValues through every field name, value, and error — no manual generics required (though you can provide them).
Core Principles:
- Type inference from defaults -
defaultValuesdefines the form shape; field names and values are fully typed - Headless - Zero UI opinions; works with any component library or native inputs
- Validation-event-driven - Validators attach to specific events (
onChange,onBlur,onSubmit) per field or per form - Framework-agnostic core - Same mental model across React, Vue, Solid, Angular, and Lit
- Composition via factory -
createFormHookshares field/form components across an app
Core Patterns
Pattern 1: Basic useForm + form.Field
Every form starts with useForm and renders fields via form.Field. The children render prop receives the field API with state, handleChange, and handleBlur.
import { useForm } from "@tanstack/react-form";
const form = useForm({
defaultValues: { name: "", email: "" },
onSubmit: async ({ value }) => {
await submitToApi(value);
},
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.Field
name="email"
children={(field) => (
<input
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
/>
</form>
);
Key difference from other form libraries: No register, no Controller, no ref forwarding. You always use field.handleChange and field.state.value explicitly.
See examples/core.md for complete form with error display and accessibility.
Pattern 2: Field-Level Validation
Validators are functions on the validators prop. Sync validators return a string (error) or undefined (valid). Async validators use onChangeAsync, onBlurAsync, onSubmitAsync.
<form.Field
name="age"
validators={{
onChange: ({ value }) => (value < 13 ? "Must be 13 or older" : undefined),
onBlurAsync: async ({ value }) => {
const exists = await checkAge(value);
return exists ? undefined : "Age not valid on server";
},
}}
children={(field) => (
<div>
<input
type="number"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
/>
{field.state.meta.errors.map((err) => (
<em key={err} role="alert">
{err}
</em>
))}
</div>
)}
/>
Sync-first gating: When both onBlur and onBlurAsync exist, the async validator only runs if the sync validator passes. Same for onChange/onChangeAsync.
See examples/validation.md for all validation patterns and adapter integration.
Pattern 3: Linked Fields (Cross-Field Validation)
Use onChangeListenTo to re-run a field's validator when another field changes. This solves the stale-validation problem (e.g., confirm password).
<form.Field
name="confirm_password"
validators={{
onChangeListenTo: ["password"],
onChange: ({ value, fieldApi }) => {
if (value !== fieldApi.form.getFieldValue("password")) {
return "Passwords do not match";
}
return undefined;
},
}}
children={(field) => (/* ... */)}
/>
Why this matters: Without onChangeListenTo, changing the password field does not re-validate confirm_password. The error stays stale until the user interacts with the confirm field again.
See examples/validation.md Pattern 4 for a complete linked fields example.
Pattern 4: Array Fields
Use mode="array" on form.Field to get pushValue, removeValue, swapValues, moveValue, and insertValue for dynamic field groups.
<form.Field
name="hobbies"
mode="array"
children={(hobbiesField) => (
<div>
{hobbiesField.state.value.map((_, i) => (
<div key={i}>
<form.Field
name={`hobbies[${i}].name`}
children={(field) => (
<input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
)}
/>
<button type="button" onClick={() => hobbiesField.removeValue(i)}>
Remove
</button>
</div>
))}
<button
type="button"
onClick={() => hobbiesField.pushValue({ name: "" })}
>
Add hobby
</button>
</div>
)}
/>
Important: pushValue requires a complete object matching the array item shape. Partial objects will cause type errors.
See examples/arrays.md for a complete dynamic list form.
Pattern 5: Form-Level Validation
Validators on useForm apply to the entire form. Use onSubmitAsync for server-side validation that returns field-specific errors.
const form = useForm({
defaultValues: { username: "", age: 0 },
validators: {
onSubmitAsync: async ({ value }) => {
const errors = await validateOnServer(value);
if (errors) {
return {
form: "Submission failed",
fields: {
username: errors.username,
age: errors.age,
},
};
}
return null;
},
},
});
Return shape: { form?: string, fields: Record<string, string> } — the form key is optional for form-level errors, fields maps field names to their error messages. Return null when valid.
See examples/validation.md Pattern 3 for complete form-level validation.
Pattern 6: createFormHook (App-Wide Composition)
Use createFormHook to share custom field components and form components across the app. This eliminates boilerplate and enforces consistency.
import { createFormHookContexts, createFormHook } from "@tanstack/react-form";
export const { fieldContext, formContext, useFieldContext } =
createFormHookContexts();
export const { useAppForm, withForm } = createFormHook({
fieldContext,
formContext,
fieldComponents: {
TextField: TextFieldComponent,
SelectField: SelectFieldComponent,
},
formComponents: {
SubmitButton: SubmitButtonComponent,
},
});
Usage: useAppForm accepts all useForm options. Registered fieldComponents and formComponents are available on the returned form instance: form.AppField for custom field components, form.AppForm for form-level components.
See examples/composition.md for the full factory setup and custom component patterns.
Pattern 7: Listeners (Side Effects)
Listeners react to field events and perform side effects like resetting related fields. Use the listeners prop on form.Field.
<form.Field
name="country"
listeners={{
onChange: ({ value }) => {
form.setFieldValue("province", "");
},
}}
children={(field) => (/* ... */)}
/>
Available events: onChange, onBlur, onMount, onSubmit. Listeners are for side effects only — they do not return validation errors.
See examples/composition.md Pattern 3 for a complete country/province cascade.
Pattern 8: form.Subscribe for Reactive UI
Use form.Subscribe to reactively render UI based on form state without re-rendering the entire form. Takes a selector to pick specific state.
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit || isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
)}
/>
Why this matters: Without form.Subscribe, reading form.state directly causes the parent component to re-render on every state change. The selector narrows the subscription.
<red_flags>
RED FLAGS
High Priority Issues:
- Using
registerorControllerpatterns — TanStack Form usesform.Fieldwithchildrenrender prop, not register/Controller - Missing
defaultValuesinuseForm— types cannot be inferred, fields start asundefined - Calling
form.handleSubmit()withoute.preventDefault()— causes page reload - Reading
form.statedirectly in the component body — causes full re-render on every change; useform.SubscribeoruseStore
Medium Priority Issues:
- Using
onChangevalidator for expensive checks — useonChangeAsyncwith debounce oronBlurAsyncinstead - Providing partial objects to
pushValuein array fields — must provide complete objects matching the array item type - Not using
onChangeListenTofor cross-field validation — related field errors go stale - Wrapping
form.handleSubmit()in another async function without error handling —handleSubmitdoes not catch errors thrown inonSubmit
Gotchas & Edge Cases:
field.state.meta.errorsis always an array — never compare with===, always.map()or.length- Sync validators gate async validators — if
onChangefails,onChangeAsyncdoes not run - Form-level
onSubmitAsyncvalidator returns{ fields: { fieldName: "error" } }— not the same shape as field-level validators field.state.meta.isTouchedonly becomestrueafterhandleBlurfires — not on firsthandleChange- Array field access uses bracket notation:
name={items[${i}].name}— not dot notation likeitems.${i}.name form.Subscribeuses aselectorprop to pick state — passing no selector subscribes to everythingcreateFormHookcomponents are available asform.AppFieldandform.AppForm— not onform.Field
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST provide defaultValues to useForm — TanStack Form infers field types from them)
(You MUST use form.Field with the children render prop — TanStack Form does not use register or Controller)
(You MUST use the validators prop for validation — NOT inline rules or external resolver wrappers)
(You MUST handle field.state.meta.errors as an array — always .map() over errors)
(You MUST call form.handleSubmit() inside the form's onSubmit handler with e.preventDefault())
Failure to follow these rules will break form state, lose type safety, and produce incorrect validation behavior.
</critical_reminders>
Frequently asked questions
What to verify before installation and use
What does the web-forms-tanstack-form source document cover?
Quick Guide: Use useForm with defaultValues and typed generics. Render fields with form.Field using the render-prop children pattern. Validation lives in the validators prop on both form and field level — use onChange, onBlur, onSubmit (sync) and their Async variants. Use mode="…
How do I install web-forms-tanstack-form?
The source record exposes this install command: npx skills add https://github.com/agents-inc/skills --skill "src/skills/web-forms-tanstack-form". Inspect the command and pinned source before running it.
Alternatives
Compare before choosing
coreyhaines31/marketingskills
ab-testing
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program
coreyhaines31/marketingskills
churn-prevention
When the user wants to reduce churn, build cancellation flows, set up save offers, recover failed payments, or implement retention strategies. Also use when the user mentions 'churn,' 'cancel flow,' 'offboarding,' 'save offer,' 'dunning,' 'failed payment recovery,' 'win-back,' 'retention,' 'exit survey,' 'pause subscription,' 'involuntary churn,' 'people keep canceling,' 'churn rate is too high,' 'how do I keep users,' or 'customers are leaving.' Use this whenever someone is losing subscribers o
prowler-cloud/prowler
postgresql-indexing
PostgreSQL indexing best practices for Prowler: index design, partial indexes, partitioned table indexing, EXPLAIN ANALYZE validation, concurrent operations, monitoring, and maintenance. Trigger: When creating or modifying PostgreSQL indexes, analyzing query performance with EXPLAIN, debugging slow queries, reviewing index usage statistics, reindexing, dropping indexes, or working with partitioned table indexes. Also trigger when discussing index strategies, partial indexes, or index maintenance
oaustegard/claude-skills
featuring
Generate hierarchical _FEATURES.md files that describe what a codebase DOES from a user/consumer perspective, anchored to source symbols via tree-sitting. Supports large complex codebases through feature-driven decomposition into sub-feature files. Uses a multi-pass synthesis: orientation → detail → overview rewrite. Use when someone says "what does this do", "document features", "feature inventory", "_FEATURES.md", or needs to understand a codebase's purpose before modifying it. Complements tre