kvnwolf

tanstack-form

Build type-safe, accessible forms with TanStack Form and Base UI Field. Consult this skill whenever creating forms, adding form validation, using useAppForm, adding or modifying form fields, handling form submission, or any work involving form state management.

kvnwolf 4 Updated 3mo ago
GitHub

Install

npx skillscat add kvnwolf/devtools/tanstack-form

Install via the SkillsCat registry.

SKILL.md

TanStack Form

This project uses TanStack Form with Base UI Field for accessible, type-safe forms. The app-level useAppForm hook (from @/components/ui/form) provides pre-configured form and field components that integrate with shadcn's Button and Label.

Creating a Form

import { z } from "zod";
import { useAppForm } from "@/components/ui/form";

function MyForm() {
  const form = useAppForm({
    defaultValues: {
      email: "",
      name: "",
    },
    validators: {
      onChange: z.object({
        email: z.string().email("Invalid email"),
        name: z.string().min(2, "Name too short"),
      }),
    },
    onSubmit: ({ value }) => {
      console.log(value);
    },
  });

  return (
    <form.Root form={form}>
      <form.AppField name="email">
        {(field) => (
          <field.Root>
            <field.Label>Email</field.Label>
            <field.Control render={<Input />} />
            <field.ErrorMessage />
          </field.Root>
        )}
      </form.AppField>
      <form.AppField name="name">
        {(field) => (
          <field.Root>
            <field.Label>Name</field.Label>
            <field.Control render={<Input />} />
            <field.ErrorMessage />
          </field.Root>
        )}
      </form.AppField>
      <form.Submit>Submit</form.Submit>
    </form.Root>
  );
}

Available Components

Form Components

Component Description
form.Root Wrapper that provides form context and handles submit
form.AppField Creates a field with access to field components
form.Submit Submit button that auto-disables when pristine/invalid/submitting
form.Subscribe Subscribe to form state for custom rendering

Field Components (inside form.AppField)

Component Description
field.Root Wraps field, connects aria-invalid and aria-describedby
field.Label Label that auto-connects to input via for/id
field.Control Input wrapper that handles value/onChange binding
field.ErrorMessage Shows validation errors

Polymorphic Fields with render

Field components use the render prop to customize the underlying element:

<field.Root render={<InputGroup.Root />}>
  <field.Control render={<InputGroup.Input placeholder="Email" />} />
</field.Root>

This integrates with existing UI components while maintaining form state binding.

Validation

Use a Zod schema for validation — it provides type inference and consistent error messages:

const form = useAppForm({
  defaultValues: { email: "" },
  validators: {
    onChange: z.object({
      email: z.string().email("Invalid email"),
    }),
  },
});

Programmatic Control

// Reset form to default values
form.reset();

// Set field value
form.setFieldValue("email", "new@email.com");

// Get current values
const values = form.state.values;

// Trigger validation
form.validate();

Accessibility

Base UI Field automatically handles:

  • aria-invalid on invalid fields
  • aria-describedby linking inputs to error messages
  • for/id linking labels to inputs
  • Disabled state during form submission