Skip to main content
This guide will walk you through creating your first form with TanStack Form. We’ll use React as the example framework, but the concepts apply to all supported frameworks.
If you haven’t installed TanStack Form yet, check out the Installation guide first.

Your First Form

1

Import the form hook

Import the useForm hook from the TanStack Form package for your framework:
import { useForm } from '@tanstack/react-form'
2

Create a form instance

Create a form instance with useForm, providing default values and a submit handler:
export default function App() {
  const form = useForm({
    defaultValues: {
      firstName: '',
      lastName: '',
    },
    onSubmit: async ({ value }) => {
      // Do something with form data
      console.log(value)
    },
  })
  
  // ... rest of component
}
3

Create form fields

Use form.Field to create type-safe form fields with validation:
<form
  onSubmit={(e) => {
    e.preventDefault()
    e.stopPropagation()
    form.handleSubmit()
  }}
>
  <form.Field
    name="firstName"
    validators={{
      onChange: ({ value }) =>
        !value
          ? 'A first name is required'
          : value.length < 3
            ? 'First name must be at least 3 characters'
            : undefined,
    }}
    children={(field) => (
      <>
        <label htmlFor={field.name}>First Name:</label>
        <input
          id={field.name}
          name={field.name}
          value={field.state.value}
          onBlur={field.handleBlur}
          onChange={(e) => field.handleChange(e.target.value)}
        />
        {field.state.meta.isTouched && field.state.meta.errors.length > 0 && (
          <em>{field.state.meta.errors.join(', ')}</em>
        )}
      </>
    )}
  />
</form>
4

Add a submit button

Use form.Subscribe to create a submit button that responds to form state:
<form.Subscribe
  selector={(state) => [state.canSubmit, state.isSubmitting]}
  children={([canSubmit, isSubmitting]) => (
    <button type="submit" disabled={!canSubmit}>
      {isSubmitting ? '...' : 'Submit'}
    </button>
  )}
/>

Complete Example

Here’s a complete working example with two fields and validation:
import { useForm } from '@tanstack/react-form'
import type { AnyFieldApi } from '@tanstack/react-form'

function FieldInfo({ field }: { field: AnyFieldApi }) {
  return (
    <>
      {field.state.meta.isTouched && !field.state.meta.isValid ? (
        <em>{field.state.meta.errors.join(',')}</em>
      ) : null}
      {field.state.meta.isValidating ? 'Validating...' : null}
    </>
  )
}

export default function App() {
  const form = useForm({
    defaultValues: {
      firstName: '',
      lastName: '',
    },
    onSubmit: async ({ value }) => {
      // Do something with form data
      console.log(value)
    },
  })

  return (
    <div>
      <h1>Simple Form Example</h1>
      <form
        onSubmit={(e) => {
          e.preventDefault()
          e.stopPropagation()
          form.handleSubmit()
        }}
      >
        <div>
          <form.Field
            name="firstName"
            validators={{
              onChange: ({ value }) =>
                !value
                  ? 'A first name is required'
                  : value.length < 3
                    ? 'First name must be at least 3 characters'
                    : undefined,
              onChangeAsyncDebounceMs: 500,
              onChangeAsync: async ({ value }) => {
                await new Promise((resolve) => setTimeout(resolve, 1000))
                return (
                  value.includes('error') && 'No "error" allowed in first name'
                )
              },
            }}
            children={(field) => (
              <>
                <label htmlFor={field.name}>First Name:</label>
                <input
                  id={field.name}
                  name={field.name}
                  value={field.state.value}
                  onBlur={field.handleBlur}
                  onChange={(e) => field.handleChange(e.target.value)}
                />
                <FieldInfo field={field} />
              </>
            )}
          />
        </div>
        <div>
          <form.Field
            name="lastName"
            children={(field) => (
              <>
                <label htmlFor={field.name}>Last Name:</label>
                <input
                  id={field.name}
                  name={field.name}
                  value={field.state.value}
                  onBlur={field.handleBlur}
                  onChange={(e) => field.handleChange(e.target.value)}
                />
                <FieldInfo field={field} />
              </>
            )}
          />
        </div>
        <form.Subscribe
          selector={(state) => [state.canSubmit, state.isSubmitting]}
          children={([canSubmit, isSubmitting]) => (
            <>
              <button type="submit" disabled={!canSubmit}>
                {isSubmitting ? '...' : 'Submit'}
              </button>
              <button
                type="reset"
                onClick={(e) => {
                  e.preventDefault()
                  form.reset()
                }}
              >
                Reset
              </button>
            </>
          )}
        />
      </form>
    </div>
  )
}

Key Concepts

Form Instance

The form instance created by useForm (or equivalent in other frameworks) is the central hub for managing form state. It provides:
  • form.Field - Component for creating type-safe fields
  • form.Subscribe - Component for subscribing to form state
  • form.handleSubmit() - Method to handle form submission
  • form.reset() - Method to reset the form

Field Validation

TanStack Form supports multiple validation strategies:
  • onChange - Validates on every change
  • onBlur - Validates when field loses focus
  • onChangeAsync - Asynchronous validation with debouncing
  • onMount - Validates when field is mounted

Field State

Each field has access to its state through field.state:
  • field.state.value - Current field value
  • field.state.meta.errors - Validation errors
  • field.state.meta.isTouched - Whether field has been touched
  • field.state.meta.isValidating - Whether field is currently validating

Framework-Specific Examples

While this guide uses React, TanStack Form works with all major frameworks:

Vue

Use useForm from @tanstack/vue-form

Angular

Use injectForm from @tanstack/angular-form

Solid

Use createForm from @tanstack/solid-form

Svelte

Use createForm from @tanstack/svelte-form

Next Steps

Now that you’ve created your first form, explore more advanced features:

Build docs developers (and LLMs) love