Source profileQuality 92/100

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="…

Best for

    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

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    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.

    Source-detected install commandSource
    npx skills add https://github.com/agents-inc/skills --skill "src/skills/web-forms-tanstack-form"
    Safe inspection promptEditorial

    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

    1. 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)
    2. 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
    3. 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.
    4. 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.
    5. 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

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score92/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars23SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated 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 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="array" for dynamic field lists with pushValue/removeValue. Use onChangeListenTo for cross-field validation. For app-wide consistency, create a shared useAppForm via createFormHook. Always provide defaultValues — 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:


    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:

    1. Type inference from defaults - defaultValues defines the form shape; field names and values are fully typed
    2. Headless - Zero UI opinions; works with any component library or native inputs
    3. Validation-event-driven - Validators attach to specific events (onChange, onBlur, onSubmit) per field or per form
    4. Framework-agnostic core - Same mental model across React, Vue, Solid, Angular, and Lit
    5. Composition via factory - createFormHook shares 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 register or Controller patterns — TanStack Form uses form.Field with children render prop, not register/Controller
    • Missing defaultValues in useForm — types cannot be inferred, fields start as undefined
    • Calling form.handleSubmit() without e.preventDefault() — causes page reload
    • Reading form.state directly in the component body — causes full re-render on every change; use form.Subscribe or useStore

    Medium Priority Issues:

    • Using onChange validator for expensive checks — use onChangeAsync with debounce or onBlurAsync instead
    • Providing partial objects to pushValue in array fields — must provide complete objects matching the array item type
    • Not using onChangeListenTo for cross-field validation — related field errors go stale
    • Wrapping form.handleSubmit() in another async function without error handling — handleSubmit does not catch errors thrown in onSubmit

    Gotchas & Edge Cases:

    • field.state.meta.errors is always an array — never compare with ===, always .map() or .length
    • Sync validators gate async validators — if onChange fails, onChangeAsync does not run
    • Form-level onSubmitAsync validator returns { fields: { fieldName: "error" } } — not the same shape as field-level validators
    • field.state.meta.isTouched only becomes true after handleBlur fires — not on first handleChange
    • Array field access uses bracket notation: name={items[${i}].name} — not dot notation like items.${i}.name
    • form.Subscribe uses a selector prop to pick state — passing no selector subscribes to everything
    • createFormHook components are available as form.AppField and form.AppForm — not on form.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

    Computed 10045,960

    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

    Computed 10045,960

    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

    Computed 10014,706

    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

    Computed 100147

    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