shadcn-tanstack-form is a production-ready integration that combines shadcn/ui’s form components with TanStack Form’s state management capabilities.
The library brings together two popular React ecosystems to deliver type-safe form handling with pre-styled, accessible UI components.
Features
๐ฏ Full TypeScript support with automatic type inference from form schemas to field values.
๐ Direct integration with TanStack Form’s reactive state management and validation system.
๐จ Pre-styled form components from shadcn/ui that follow accessibility best practices.
โ Schema validation support for libraries, including Zod with automatic error display.
๐งช Testing utilities and test suite for form validation and submission flows.
๐ Flexible component architecture that allows mixing TanStack components with standard shadcn/ui elements.
Use Cases
- Complex Registration Forms: You can build multi-field user registration forms with specific validation rules for each field, such as password strength and email format.
- Multi-Step Wizards: The state management is suitable for creating multi-step forms, like user onboarding or checkout processes, where data is collected across several steps.
- Application Settings Pages: You can construct detailed settings pages that include various input types, such as text fields, checkboxes, and selects, with instant validation feedback.
- Business Data Entry: It is useful for creating data-heavy forms for business applications where data integrity and validation are critical.
How to Use It
1. Install and initialize the necessary shadcn/ui:
pnpm dlx shadcn-ui@latest init2. Add the TanStack Form library.
pnpm add @tanstack/react-form3. Install Radix UI Slot:
pnpm add @radix-ui/react-slot4. Copy the form.tsx file from the repository into your components/ui/ directory. This file contains the integrated form components.
5. Copy the use-form.tsx hook into your hooks/ directory. This hook connects TanStack Form’s logic with the UI components.
6. Install the specific shadcn/ui components you will use in your forms.
pnpm dlx shadcn-ui@latest add button input label checkbox7. To create a simple form, you use the useAppForm hook to define default values and an onSubmit handler. The form structure is built with components like Form, Field, FieldLabel, and FieldControl.
import { useAppForm } from "@/hooks/use-form";
import {
Form,
Field,
FieldLabel,
FieldControl,
FieldDescription,
FieldError,
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
export function BasicForm() {
const form = useAppForm({
defaultValues: {
username: "",
},
onSubmit: async ({ value }) => {
console.log("Submitted:", value);
},
});
return (
<form.AppForm>
<Form className="space-y-4">
<form.AppField
name="username"
validators={{
onChange: ({ value }: { value: string }) =>
!value ? "Username is required" : undefined,
}}
>
{(field) => (
<Field>
<FieldLabel>Username</FieldLabel>
<FieldControl>
<Input
placeholder="Enter your username"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</FieldControl>
<FieldError />
</Field>
)}
</form.AppField>
<Button type="submit">Submit</Button>
</Form>
</form.AppForm>
);
}8. For more complex validation, you can integrate a schema library like Zod. Define a schema and pass it to the validators option in the useAppForm hook. The form will automatically handle type inference and validation based on your schema.
import { z } from "zod";
import { useAppForm } from "@/hooks/use-form";
import {
Form,
Field,
FieldLabel,
FieldControl,
FieldError,
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
const signupSchema = z.object({
email: z.string().email("Invalid email address"),
acceptTerms: z.boolean().refine((val) => val, {
message: "You must accept the terms",
}),
});
type SignupFormData = z.infer<typeof signupSchema>;
export function ZodValidationForm() {
const form = useAppForm({
defaultValues: {
email: "",
acceptTerms: false,
} as SignupFormData,
validators: {
onChange: signupSchema,
},
onSubmit: async ({ value }) => {
console.log("Account created:", value);
},
});
return (
<form.AppForm>
<Form className="space-y-6">
<form.AppField name="email">
{(field) => (
<Field>
<FieldLabel>Email</FieldLabel>
<FieldControl>
<Input
placeholder="[email protected]"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
</FieldControl>
<FieldError />
</Field>
)}
</form.AppField>
<form.AppField name="acceptTerms">
{(field) => (
<Field className="flex items-center space-x-2">
<FieldControl>
<Checkbox
checked={field.state.value}
onCheckedChange={(checked) => field.handleChange(checked)}
/>
</FieldControl>
<FieldLabel>Accept terms and conditions</FieldLabel>
<FieldError />
</Field>
)}
</form.AppField>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<Button type="submit" disabled={!canSubmit}>
{isSubmitting ? "Creating..." : "Create Account"}
</Button>
)}
</form.Subscribe>
</Form>
</form.AppForm>
);
}Related Resources
- TanStack Form Documentation covers the complete API reference for form state management, validation strategies, and advanced patterns like arrays and nested objects.
- shadcn/ui Documentation provides setup guides and component documentation for the UI library, including theming and customization options.
- Zod Documentation explains schema definition and validation rules for type-safe form validation.
FAQs
Q: Can I use this integration with other validation libraries besides Zod?
A: Yes, TanStack Form accepts any validation function that returns undefined for valid input or an error string for invalid input. You can use Yup, Joi, or custom validation logic. Schema validators should be passed to the validators.onChange property, and you can mix schema validation with field-level validators.
Q: How do I handle form arrays for dynamic lists of fields?
A: TanStack Form provides array field methods through the form.useField hook. You can push, remove, and update array items while maintaining proper state management and validation. The integration works with these array methods, and you render array items using standard React mapping.
Q: Can I customize the styling of form components?
A: shadcn/ui components are designed to be copied into your project and customized. You can modify the component files directly or override styles using Tailwind classes. The integration maintains the same customization approach as standard shadcn/ui components.
Q: How do I access form state outside the form component?
A: Store the form instance in a ref or state at a parent component level, then pass it to child components. TanStack Form provides methods to read and update form state programmatically. You can also use the form.Subscribe component to listen for state changes anywhere in your component tree.






