Forms/TanStack Form

TanStack Form

Form integration with TanStack Form library.

Themed

When to Use

Use TanStack Form when you need to:

  • Build type-safe forms with fine-grained field-level subscriptions for minimal re-renders
  • Implement async validation with built-in debouncing (e.g., checking username availability)
  • Share form logic across frameworks (React, Vue, Solid, Angular)
  • Keep bundle size minimal while retaining full form capabilities (~5kb gzipped)
  • Manage complex multi-step or conditional form workflows with headless control

When Not to Use

  • Simple forms with 1-2 fields and no validation - use native HTML form handling or Field
  • Projects already committed to React Hook Form - use React Hook Form with the shadcn/ui Form component
  • Schema-first validation with Zod is the primary requirement - the Form component has tighter Zod integration
  • Static forms with no client-side validation - use a server action with a plain <form>

Basic Form

Simple form with a single input, synchronous validation, and submit state management.

Your unique display name.

Multi-field Form

Form with multiple fields arranged in a responsive grid layout with per-field validation.

With Select

Integrating a Select dropdown component with TanStack Form using onValueChange for controlled state.

With Checkboxes

Boolean fields using Checkbox components for toggleable options like terms acceptance.

Get updates about new features and releases.

Required to create an account.

Async Validation

Debounced async validation for checking server-side constraints. Try 'admin' or 'root' to see validation errors.

Try "admin" or "root" to see async validation.

UX & Design Guidelines

Visual Hierarchy

Group related fields together with clear visual sections. Use space-y-6 between field groups and space-y-2 between label, input, and error message within a single field. Place the primary submit action at the bottom of the form and align it to the left for top-to-bottom reading flow.

Spacing & Layout

Use grid grid-cols-2 gap-4 for related short fields like first and last name. Keep forms at max-w-sm for single-column forms or max-w-lg for multi-column forms. Always constrain form width to prevent inputs from stretching too wide on desktop viewports.

Responsive Behavior

Multi-column grids should collapse to a single column on mobile. Use grid-cols-1 md:grid-cols-2 for responsive field layouts. Submit buttons should be full-width (w-full) on mobile for easier touch targets and inline on larger screens.

Color & Contrast

Error messages use text-destructive for immediate visual feedback. Description text uses text-muted-foreground to differentiate from primary content. The async validation "Checking..." indicator uses muted foreground to avoid alarming the user while a background check is in progress.

Accessibility

Keyboard Navigation

  • Tab — Move focus between form fields in document order
  • Shift + Tab — Move focus to the previous field
  • Enter — Submit the form when a submit button has focus
  • Space — Toggle checkbox fields and activate buttons

Screen Reader Support

  • Every input must have an associated Label with matching htmlFor and id attributes
  • Error messages should use role="alert" so screen readers announce them when they appear
  • Description text should be linked to the input via aria-describedby
  • The async "Checking..." status should use aria-live="polite" for non-intrusive announcements

Focus Management

Inputs display a visible focus ring using focus-visible:ring-[3px]. On validation failure after form submission, focus should be programmatically moved to the first invalid field so the user can correct it immediately. Use field.state.meta.errors to determine which field needs attention.

Error Announcements

Mark invalid inputs with aria-invalid="true" when errors are present. Link error messages to their inputs using aria-describedby pointing to the error element's id. This ensures assistive technology users can discover what went wrong without visual cues.

ARIA Attributes

{/* Associate labels with fields using htmlFor */}
<Label htmlFor={field.name}>Username</Label>
<Input
  id={field.name}
  name={field.name}
  aria-invalid={field.state.meta.errors.length > 0}
  aria-describedby={`${field.name}-error ${field.name}-description`}
  value={field.state.value}
  onChange={(e) => field.handleChange(e.target.value)}
/>

{/* Error message with matching id */}
{field.state.meta.errors.length > 0 && (
  <p id={`${field.name}-error`} role="alert" className="text-sm text-destructive">
    {field.state.meta.errors[0]}
  </p>
)}

{/* Description with matching id */}
<p id={`${field.name}-description`} className="text-sm text-muted-foreground">
  Your unique display name.
</p>

API Reference

The useForm hook accepts the following options for configuring form behavior.

PropTypeDefaultDescription
defaultValuesrequiredTFormDataThe initial values for the form fields. Infers TypeScript types for all field names.
onSubmitrequired({ value, formApi }) => void | Promise<void>Callback fired when the form is submitted and all validation passes.
validatorsFormValidators{}Form-level validators that run on the entire form state (onChange, onBlur, onSubmit).
validatorAdapterValidatorAdapterundefinedOptional adapter for schema-based validation libraries like Zod or Valibot.
onSubmitInvalid({ value, formApi }) => voidundefinedCallback fired when submission is attempted but validation fails.
asyncDebounceMsnumber0Default debounce time in milliseconds for all async validators on the form.

Field Props

The form.Field component accepts the following props for individual field configuration.

PropTypeDefaultDescription
namerequiredkeyof TFormDataThe field name. Must match a key from the form's defaultValues.
childrenrequired(fieldApi: FieldApi) => ReactNodeRender function that receives the field API with state, handlers, and metadata.
validatorsFieldValidators{}Field-level validators (onChange, onBlur, onChangeAsync, onBlurAsync) with optional debounce.
defaultValueTFieldValueundefinedOverride the default value for this specific field.
asyncDebounceMsnumber0Debounce time in milliseconds for async validators on this field.