"use client"
import { toast } from "sonner"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
getInputFieldProps,
useForm,
} from "@/components/ui/tanstack-form"
const accountSchema = z.object({
email: z.string().email("Invalid email"),
password: z.string().min(8, "Password must be at least 8 characters"),
})
export function TanstackFormDemo() {
const form = useForm({
schema: accountSchema,
defaultValues: {
email: "",
password: "",
},
onSubmit: async (values) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
toast.success("Account created successfully!")
console.log(values)
},
})
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-lg font-semibold">Create Account</CardTitle>
<CardDescription>TanStack Form with Zod validation</CardDescription>
</CardHeader>
<CardContent>
<Form form={form} className="space-y-4">
<FormField form={form} name="email">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
{...getInputFieldProps(field)}
type="email"
placeholder="you@example.com"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
<FormField form={form} name="password">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
{...getInputFieldProps(field)}
type="password"
placeholder="••••••••"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
<Button type="submit" className="w-full">
Create Account
</Button>
</Form>
</CardContent>
</Card>
)
}
Installation
pnpm dlx shadcn@latest add https://ui.nowts.app/r/tanstack-form.json
About
The TanStack Form component is a complete form management solution built on top of TanStack Form with Zod schema validation, providing type-safe forms with automatic field validation and error handling.
Features
- ✅ Full TypeScript support: Type-safe form data with automatic inference from Zod schemas
- ✅ Zod validation: Schema-based validation with custom error messages
- ✅ All input types: Input, Textarea, Select, Checkbox, RadioGroup, Switch
- ✅ Array fields: Dynamic arrays with add/remove functionality
- ✅ Nested objects: Deep object paths with type-safe field names
- ✅ Field helpers: Pre-built helpers for all input types
- ✅ Validation modes: onBlur, onChange, or onSubmit validation
- ✅ Form state: Access to submitting, valid, dirty states
Usage
Basic form with Input
"use client"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
getInputFieldProps,
useForm,
} from "@/registry/nowts/ui/tanstack-form"
const schema = z.object({
email: z.string().email("Please enter a valid email address"),
name: z.string().min(2, "Name must be at least 2 characters"),
})
export function BasicForm() {
const form = useForm({
schema,
defaultValues: {
email: "",
name: "",
},
onSubmit: async (values) => {
console.log(values)
},
})
return (
<Form form={form} className="space-y-4">
<FormField form={form} name="name">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...getInputFieldProps(field)} placeholder="John Doe" />
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
<FormField form={form} name="email">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
{...getInputFieldProps(field)}
type="email"
placeholder="john@example.com"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
<Button type="submit" disabled={form.state.isSubmitting}>
{form.state.isSubmitting ? "Submitting..." : "Submit"}
</Button>
</Form>
)
}
Textarea field
import { Textarea } from "@/components/ui/textarea"
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
getTextareaFieldProps,
} from "@/registry/nowts/ui/tanstack-form"
const schema = z.object({
bio: z
.string()
.min(10, "Bio must be at least 10 characters")
.max(500, "Bio must be at most 500 characters"),
})
// Inside your form:
<FormField form={form} name="bio">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Bio</FormLabel>
<FormControl>
<Textarea
{...getTextareaFieldProps(field)}
placeholder="Tell us about yourself..."
rows={4}
/>
</FormControl>
<FormDescription>
A brief description about yourself (10-500 characters)
</FormDescription>
<FormMessage />
</FormItem>
)}
</FormField>
Select field
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
getSelectFieldProps,
} from "@/registry/nowts/ui/tanstack-form"
const schema = z.object({
role: z.string().min(1, "Please select a role"),
})
// Inside your form:
<FormField form={form} name="role">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Role</FormLabel>
<FormControl>
<Select {...getSelectFieldProps(field)}>
<SelectTrigger>
<SelectValue placeholder="Select your role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="user">User</SelectItem>
<SelectItem value="guest">Guest</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
Checkbox field
import { Checkbox } from "@/components/ui/checkbox"
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
getCheckboxFieldProps,
} from "@/registry/nowts/ui/tanstack-form"
const schema = z.object({
consent: z
.boolean()
.refine((val) => val === true, "You must accept the terms to continue"),
})
// Inside your form:
<FormField form={form} name="consent">
{(field) => (
<FormItem field={field} form={form}>
<div className="flex items-start gap-2">
<FormControl>
<Checkbox {...getCheckboxFieldProps(field)} id="consent" />
</FormControl>
<div className="flex flex-col gap-1">
<FormLabel htmlFor="consent" className="font-normal">
I agree to receive marketing emails and accept the terms and
conditions
</FormLabel>
<FormMessage />
</div>
</div>
</FormItem>
)}
</FormField>
RadioGroup field
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import {
FormField,
FormItem,
FormLabel,
FormMessage,
getRadioGroupFieldProps,
} from "@/registry/nowts/ui/tanstack-form"
const schema = z.object({
preferredContact: z.string().min(1, "Please select a contact method"),
})
// Inside your form:
<FormField form={form} name="preferredContact">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Preferred Contact Method</FormLabel>
<RadioGroup {...getRadioGroupFieldProps(field)}>
<div className="flex items-center space-x-2">
<RadioGroupItem value="email" id="contact-email" />
<Label htmlFor="contact-email" className="font-normal">
Email
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="phone" id="contact-phone" />
<Label htmlFor="contact-phone" className="font-normal">
Phone
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="slack" id="contact-slack" />
<Label htmlFor="contact-slack" className="font-normal">
Slack
</Label>
</div>
</RadioGroup>
<FormMessage />
</FormItem>
)}
</FormField>
Switch field
import { Switch } from "@/components/ui/switch"
import {
FormDescription,
FormField,
FormItem,
FormLabel,
getSwitchFieldProps,
} from "@/registry/nowts/ui/tanstack-form"
const schema = z.object({
notifications: z.boolean(),
})
// Inside your form:
<FormField form={form} name="notifications">
{(field) => (
<FormItem field={field} form={form}>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<FormLabel>Notifications</FormLabel>
<FormDescription>Receive notifications about updates</FormDescription>
</div>
<Switch {...getSwitchFieldProps(field)} />
</div>
</FormItem>
)}
</FormField>
Complex Form Example
A complete registration form combining multiple field types (Input, Select, Textarea, Checkbox, Switch):
"use client"
import { toast } from "sonner"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
getCheckboxFieldProps,
getInputFieldProps,
getSelectFieldProps,
getSwitchFieldProps,
getTextareaFieldProps,
useForm,
} from "@/components/ui/tanstack-form"
const registrationSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Please enter a valid email address"),
role: z.enum(["developer", "designer", "manager"], {
required_error: "Please select a role",
}),
bio: z
.string()
.min(10, "Bio must be at least 10 characters")
.max(200, "Bio must be at most 200 characters"),
newsletter: z.boolean(),
notifications: z.boolean(),
terms: z.boolean().refine((val) => val === true, {
message: "You must accept the terms and conditions",
}),
})
export function TanstackFormComplexDemo() {
const form = useForm({
schema: registrationSchema,
defaultValues: {
name: "",
email: "",
role: "developer" as const,
bio: "",
newsletter: false,
notifications: true,
terms: false,
},
onSubmit: async (values) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
toast.success("Registration successful!", {
description: `Welcome, ${values.name}!`,
})
console.log(values)
},
})
return (
<Card className="mx-auto w-full max-w-2xl p-6">
<Form form={form} className="space-y-6">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Create Account</h3>
<p className="text-muted-foreground text-sm">
Fill out the form below to create your account
</p>
</div>
<FormField form={form} name="name">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Full Name</FormLabel>
<FormControl>
<Input {...getInputFieldProps(field)} placeholder="John Doe" />
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
<FormField form={form} name="email">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input
{...getInputFieldProps(field)}
type="email"
placeholder="john@example.com"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
<FormField form={form} name="role">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Role</FormLabel>
<FormControl>
<Select {...getSelectFieldProps(field)}>
<SelectTrigger>
<SelectValue placeholder="Select your role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="developer">Developer</SelectItem>
<SelectItem value="designer">Designer</SelectItem>
<SelectItem value="manager">Manager</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormDescription>
Choose the role that best describes you
</FormDescription>
<FormMessage />
</FormItem>
)}
</FormField>
<FormField form={form} name="bio">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Bio</FormLabel>
<FormControl>
<Textarea
{...getTextareaFieldProps(field)}
placeholder="Tell us about yourself..."
rows={4}
/>
</FormControl>
<FormDescription>
Write a short bio (10-200 characters)
</FormDescription>
<FormMessage />
</FormItem>
)}
</FormField>
<FormField form={form} name="newsletter">
{(field) => (
<FormItem field={field} form={form}>
<div className="flex items-start gap-3">
<FormControl>
<Checkbox {...getCheckboxFieldProps(field)} id="newsletter" />
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel htmlFor="newsletter" className="font-normal">
Subscribe to newsletter
</FormLabel>
<FormDescription>
Receive weekly updates about new features
</FormDescription>
</div>
</div>
<FormMessage />
</FormItem>
)}
</FormField>
<FormField form={form} name="notifications">
{(field) => (
<FormItem field={field} form={form}>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel>Push Notifications</FormLabel>
<FormDescription>
Receive push notifications about account activity
</FormDescription>
</div>
<FormControl>
<Switch {...getSwitchFieldProps(field)} />
</FormControl>
</div>
</FormItem>
)}
</FormField>
<FormField form={form} name="terms">
{(field) => (
<FormItem field={field} form={form}>
<div className="flex items-start gap-3">
<FormControl>
<Checkbox {...getCheckboxFieldProps(field)} id="terms" />
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel htmlFor="terms" className="font-normal">
I accept the terms and conditions
</FormLabel>
<FormDescription>
You agree to our Terms of Service and Privacy Policy
</FormDescription>
<FormMessage />
</div>
</div>
</FormItem>
)}
</FormField>
<Button
type="submit"
disabled={form.state.isSubmitting}
className="w-full"
>
{form.state.isSubmitting ? "Creating account..." : "Create Account"}
</Button>
</Form>
</Card>
)
}
Advanced Usage
Array fields
Dynamic array fields with add/remove functionality. Use mode="array"
on FormField and access field.pushValue
and field.removeValue
:
"use client"
import { PlusIcon, XIcon } from "lucide-react"
import { toast } from "sonner"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
getInputFieldProps,
useForm,
} from "@/components/ui/tanstack-form"
const teamSchema = z.object({
teamName: z.string().min(2, "Team name must be at least 2 characters"),
users: z
.array(
z.object({
email: z.string().email("Please enter a valid email address"),
})
)
.min(1, "You must add at least one user")
.max(10, "Maximum 10 users allowed"),
})
export function TanstackFormArrayDemo() {
const form = useForm({
schema: teamSchema,
defaultValues: {
teamName: "",
users: [{ email: "" }],
},
onSubmit: async (values) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
toast.success("Team created!", {
description: `${values.teamName} with ${values.users.length} members`,
})
console.log(values)
},
})
return (
<Card className="mx-auto w-full max-w-2xl p-6">
<Form form={form} className="space-y-6">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Create Team</h3>
<p className="text-muted-foreground text-sm">
Add team members by email address
</p>
</div>
<FormField form={form} name="teamName">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Team Name</FormLabel>
<FormControl>
<Input
{...getInputFieldProps(field)}
placeholder="Engineering Team"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
<FormField form={form} name="users" mode="array">
{(usersField) => (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h4 className="text-sm font-medium">Team Members</h4>
<p className="text-muted-foreground text-sm">
Add email addresses for team members (1-10 users)
</p>
</div>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => usersField.pushValue?.({ email: "" })}
disabled={usersField.state.value.length >= 10}
>
<PlusIcon className="mr-2 size-4" />
Add User
</Button>
</div>
<div className="space-y-2">
{usersField.state.value.map((_, index) => (
<FormField
key={index}
form={form}
name={`users[${index}].email`}
>
{(field) => (
<div className="flex items-start gap-2">
<div className="flex-1">
<Input
{...getInputFieldProps(field)}
type="email"
placeholder="user@example.com"
/>
{field.state.meta.isTouched &&
form.state.submissionAttempts > 0 &&
field.state.meta.errors.length > 0 && (
<p className="text-destructive mt-1 text-sm">
{typeof field.state.meta.errors[0] === "string"
? field.state.meta.errors[0]
: field.state.meta.errors[0]?.message}
</p>
)}
</div>
<Button
type="button"
size="icon"
variant="ghost"
onClick={() => usersField.removeValue?.(index)}
disabled={usersField.state.value.length === 1}
>
<XIcon className="size-4" />
</Button>
</div>
)}
</FormField>
))}
</div>
{usersField.state.meta.isTouched &&
form.state.submissionAttempts > 0 &&
usersField.state.meta.errors.length > 0 && (
<p className="text-destructive text-sm">
{typeof usersField.state.meta.errors[0] === "string"
? usersField.state.meta.errors[0]
: usersField.state.meta.errors[0]?.message}
</p>
)}
</div>
)}
</FormField>
<Button
type="submit"
disabled={form.state.isSubmitting}
className="w-full"
>
{form.state.isSubmitting ? "Creating..." : "Create Team"}
</Button>
</Form>
</Card>
)
}
Nested objects
Access deeply nested fields using dot notation. TypeScript provides full autocompletion:
const schema = z.object({
user: z.object({
profile: z.object({
firstName: z.string().min(2),
lastName: z.string().min(2),
}),
contact: z.object({
email: z.string().email(),
}),
}),
})
// Use dot notation to access nested fields
<FormField form={form} name="user.profile.firstName">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>First Name</FormLabel>
<FormControl>
<Input {...getInputFieldProps(field)} />
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
<FormField form={form} name="user.contact.email">
{(field) => (
<FormItem field={field} form={form}>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...getInputFieldProps(field)} type="email" />
</FormControl>
<FormMessage />
</FormItem>
)}
</FormField>
API Reference
useForm
Create a form instance with Zod validation:
function useForm<TSchema extends z.ZodType>({
schema: TSchema
defaultValues: z.infer<TSchema>
onSubmit: (values: z.infer<TSchema>) => void | Promise<void>
validationMode?: "onChange" | "onBlur" | "onSubmit"
}): FormApi<z.infer<TSchema>>
Parameter | Type | Description | Default |
---|---|---|---|
schema | z.ZodType | Zod schema for validation | - |
defaultValues | z.infer<TSchema> | Initial form values | - |
onSubmit | (values) => void | Promise<void> | Form submission handler | - |
validationMode | "onChange" | "onBlur" | "onSubmit" | When to trigger validation | "onBlur" |
Form Components
Form
Wrapper component that handles form submission:
<Form form={form} className="space-y-4">
{/* Form fields */}
</Form>
FormField
Connects a field to the form with type-safe field names:
<FormField form={form} name="email" mode="value">
{(field) => ({
/* Field UI */
})}
</FormField>
Parameter | Type | Description | Default |
---|---|---|---|
form | FormApi | Form instance from useForm | - |
name | string | Type-safe field name (e.g., "email") | - |
mode | "value" | "array" | Field mode (use "array" for arrays) | "value" |
FormItem
Container for field components with error state:
<FormItem field={field} form={form}>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...getInputFieldProps(field)} />
</FormControl>
<FormDescription>Your email address</FormDescription>
<FormMessage />
</FormItem>
Field Helpers
Pre-built helpers for common input types:
getInputFieldProps
For Input and Textarea components:
<Input {...getInputFieldProps(field)} type="email" />
<Textarea {...getTextareaFieldProps(field)} rows={4} />
Returns: { name, value, onChange, onBlur, "aria-invalid" }
getSelectFieldProps
For Select components:
<Select {...getSelectFieldProps(field)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>...</SelectContent>
</Select>
Returns: { name, value, onValueChange, "aria-invalid" }
getCheckboxFieldProps
For Checkbox and Switch components:
<Checkbox {...getCheckboxFieldProps(field)} />
<Switch {...getSwitchFieldProps(field)} />
Returns: { name, checked, onCheckedChange, "aria-invalid" }
getRadioGroupFieldProps
For RadioGroup components:
<RadioGroup {...getRadioGroupFieldProps(field)}>
<RadioGroupItem value="option1" />
<RadioGroupItem value="option2" />
</RadioGroup>
Returns: { name, value, onValueChange, "aria-invalid" }
Form State
Access form state through form.state
:
form.state.isSubmitting // Boolean: Is form currently submitting?
form.state.canSubmit // Boolean: Can form be submitted?
form.state.isValid // Boolean: Are all fields valid?
form.state.isDirty // Boolean: Has form been modified?
form.state.submissionAttempts // Number: How many times has submit been attempted?
form.state.values // Object: Current form values
form.state.errors // Array: Form-level validation errors
Validation Modes
Choose when validation should trigger:
// Validate on blur (default) - good balance of UX and performance
const form = useForm({ ..., validationMode: "onBlur" })
// Validate on every change - instant feedback but more expensive
const form = useForm({ ..., validationMode: "onChange" })
// Validate only on submit - minimal validation until submission
const form = useForm({ ..., validationMode: "onSubmit" })
Type Safety
The form component provides full type safety:
const schema = z.object({
email: z.string().email(),
age: z.number().min(18),
})
const form = useForm({ schema, ... })
// ✅ Valid - autocomplete works
<FormField form={form} name="email">
// ✅ Valid - autocomplete works
<FormField form={form} name="age">
// ❌ TypeScript error - field doesn't exist
<FormField form={form} name="invalid">
// ✅ Valid - nested paths work
const nestedSchema = z.object({
user: z.object({ email: z.string() })
})
<FormField form={form} name="user.email">
// ✅ Valid - array syntax works
const arraySchema = z.object({
users: z.array(z.object({ email: z.string() }))
})
<FormField form={form} name="users[0].email">
Best Practices
1. Use validation mode wisely
// For most forms, onBlur provides the best UX
const form = useForm({ validationMode: "onBlur", ... })
// For search/filter forms, onChange provides instant feedback
const searchForm = useForm({ validationMode: "onChange", ... })
2. Handle loading states
<Button type="submit" disabled={form.state.isSubmitting}>
{form.state.isSubmitting ? "Saving..." : "Save"}
</Button>
3. Provide helpful error messages
const schema = z.object({
email: z
.string()
.min(1, "Email is required")
.email("Please enter a valid email address"),
password: z
.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Password must contain at least one uppercase letter"),
})
4. Use FormDescription for hints
<FormItem field={field} form={form}>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...getInputFieldProps(field)} />
</FormControl>
<FormDescription>We'll never share your email with anyone</FormDescription>
<FormMessage />
</FormItem>
5. Reset forms after submission
onSubmit: async (values) => {
await saveData(values)
form.reset() // Reset to default values
}
Troubleshooting
Validation not triggering
- Check that you've set the correct
validationMode
- Ensure the field has been touched (blurred at least once)
- Verify that
form.state.submissionAttempts > 0
for submit validation
Type errors with field names
- Make sure the field name matches your schema exactly
- Use dot notation for nested objects:
"user.email"
- Use bracket notation for arrays:
"users[0].email"
Array fields not updating
- Make sure to use
mode="array"
on the FormField - Use
field.pushValue?.()
andfield.removeValue?.()
for array operations - Always provide unique keys when mapping over array items
On This Page
InstallationInstall dependenciesAdd the componentAboutFeaturesUsageBasic form with InputTextarea fieldSelect fieldCheckbox fieldRadioGroup fieldSwitch fieldComplex Form ExampleAdvanced UsageArray fieldsNested objectsAPI ReferenceuseFormForm ComponentsFormFormFieldFormItemField HelpersgetInputFieldPropsgetSelectFieldPropsgetCheckboxFieldPropsgetRadioGroupFieldPropsForm StateValidation ModesType SafetyBest Practices1. Use validation mode wisely2. Handle loading states3. Provide helpful error messages4. Use FormDescription for hints5. Reset forms after submissionTroubleshootingValidation not triggeringType errors with field namesArray fields not updating