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.
Install
npx skillscat add kvnwolf/devtools/tanstack-form Install via the SkillsCat registry.
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-invalidon invalid fieldsaria-describedbylinking inputs to error messagesfor/idlinking labels to inputs- Disabled state during form submission