Skip to main content
The Field component is a React component that uses the useField hook internally to manage form fields. It provides a render-prop pattern for maximum flexibility.

Import

import { Field } from '@tanstack/react-form'
// Or use the pre-bound version from form.Field

Signature

<Field
  form={formApi}
  name="fieldName"
  validators={{ ... }}
  children={(field) => ReactNode}
/>

Props

form
FormApi<TParentData>
required
The form API instance this field belongs to. Not required when using form.Field.
name
DeepKeys<TParentData>
required
The field name as a dot-notation path (e.g., "user.name", "users[0].email")
children
(field: FieldApi) => ReactNode
required
Render function that receives the field API and returns React elements
mode
'value' | 'array'
Controls reactivity behavior:
  • 'value': Re-renders on any value change (default)
  • 'array': Only re-renders when array length changes
defaultValue
TData
Default value for this field
asyncDebounceMs
number
Default debounce time in milliseconds for async validation
asyncAlways
boolean
If true, always run async validation even when sync validation fails
validators
FieldValidators
Field-level validators (same as useField validators option)
validators.onChange
FieldValidateOrFn
Validator that runs when the field value changes
validators.onChangeAsync
FieldAsyncValidateOrFn
Async validator that runs when the field value changes
validators.onChangeAsyncDebounceMs
number
Debounce time for onChange async validation
validators.onBlur
FieldValidateOrFn
Validator that runs when the field loses focus
validators.onBlurAsync
FieldAsyncValidateOrFn
Async validator that runs when the field loses focus
validators.onMount
FieldValidateOrFn
Validator that runs when the field mounts
validators.onSubmit
FieldValidateOrFn
Validator that runs on form submission
validators.onSubmitAsync
FieldAsyncValidateOrFn
Async validator that runs on form submission
listeners
FieldListeners
Field-level event listeners
listeners.onChange
(props: { value, fieldApi }) => void
Called when field value changes
listeners.onBlur
(props: { value, fieldApi }) => void
Called when field loses focus
listeners.onMount
(props: { value, fieldApi }) => void
Called when field mounts

Field API (passed to children)

The field parameter passed to the children render function has the following shape:
field
FieldApi
name
string
The field name
state
FieldState
Current field state
value
TData
Current field value
meta
FieldMeta
Field metadata including validation state and errors
handleChange
(value: TData | ((prev: TData) => TData)) => void
Update the field value
handleBlur
() => void
Mark the field as blurred
getValue
() => TData
Get the current field value
setValue
(updater: Updater<TData>, opts?: UpdateMetaOptions) => void
Set the field value programmatically
pushValue
(value: TData[number]) => void
Push a value to an array field
insertValue
(index: number, value: TData[number]) => void
Insert a value into an array field
removeValue
(index: number) => void
Remove a value from an array field
swapValues
(index1: number, index2: number) => void
Swap two values in an array field

Usage

Basic Field

import { useForm } from '@tanstack/react-form'

function MyForm() {
  const form = useForm({
    defaultValues: {
      firstName: '',
    },
  })

  return (
    <form>
      <form.Field
        name="firstName"
        children={(field) => (
          <div>
            <label htmlFor={field.name}>First Name:</label>
            <input
              id={field.name}
              value={field.state.value}
              onChange={(e) => field.handleChange(e.target.value)}
              onBlur={field.handleBlur}
            />
          </div>
        )}
      />
    </form>
  )
}

Field with Validation

import { useForm } from '@tanstack/react-form'

function MyForm() {
  const form = useForm({
    defaultValues: {
      email: '',
    },
  })

  return (
    <form>
      <form.Field
        name="email"
        validators={{
          onChange: ({ value }) =>
            !value.includes('@') ? 'Invalid email' : undefined,
          onChangeAsyncDebounceMs: 500,
          onChangeAsync: async ({ value }) => {
            await new Promise((resolve) => setTimeout(resolve, 1000))
            return value.includes('error') ? 'Email not allowed' : undefined
          },
        }}
        children={(field) => (
          <div>
            <input
              value={field.state.value}
              onChange={(e) => field.handleChange(e.target.value)}
              onBlur={field.handleBlur}
            />
            {field.state.meta.isTouched && field.state.meta.errors.length > 0 && (
              <em>{field.state.meta.errors.join(', ')}</em>
            )}
            {field.state.meta.isValidating && <span>Validating...</span>}
          </div>
        )}
      />
    </form>
  )
}

Array Field

import { useForm } from '@tanstack/react-form'

interface Person {
  name: string
  age: number
}

function MyForm() {
  const form = useForm({
    defaultValues: {
      people: [] as Person[],
    },
  })

  return (
    <form>
      <form.Field name="people" mode="array">
        {(field) => (
          <div>
            {field.state.value.map((_, i) => (
              <div key={i}>
                <form.Field name={`people[${i}].name`}>
                  {(subField) => (
                    <input
                      value={subField.state.value}
                      onChange={(e) => subField.handleChange(e.target.value)}
                    />
                  )}
                </form.Field>
                <button
                  type="button"
                  onClick={() => field.removeValue(i)}
                >
                  Remove
                </button>
              </div>
            ))}
            <button
              type="button"
              onClick={() => field.pushValue({ name: '', age: 0 })}
            >
              Add Person
            </button>
          </div>
        )}
      </form.Field>
    </form>
  )
}

Displaying Validation Errors

import { useForm } from '@tanstack/react-form'
import type { FieldApi } from '@tanstack/react-form'

function FieldInfo({ field }: { field: FieldApi<any, any, any, any> }) {
  return (
    <>
      {field.state.meta.isTouched && field.state.meta.errors.length > 0 ? (
        <em>{field.state.meta.errors.join(', ')}</em>
      ) : null}
      {field.state.meta.isValidating ? 'Validating...' : null}
    </>
  )
}

function MyForm() {
  const form = useForm({
    defaultValues: {
      username: '',
    },
  })

  return (
    <form>
      <form.Field
        name="username"
        validators={{
          onChange: ({ value }) =>
            value.length < 3 ? 'Must be at least 3 characters' : undefined,
        }}
        children={(field) => (
          <div>
            <input
              value={field.state.value}
              onChange={(e) => field.handleChange(e.target.value)}
              onBlur={field.handleBlur}
            />
            <FieldInfo field={field} />
          </div>
        )}
      />
    </form>
  )
}

Using Standalone Field Component

You can also use the standalone Field component if you need to pass the form explicitly:
import { useForm, Field } from '@tanstack/react-form'

function MyForm() {
  const form = useForm({
    defaultValues: {
      email: '',
    },
  })

  return (
    <form>
      <Field
        form={form}
        name="email"
        children={(field) => (
          <input
            value={field.state.value}
            onChange={(e) => field.handleChange(e.target.value)}
          />
        )}
      />
    </form>
  )
}

TypeScript

import { useForm } from '@tanstack/react-form'

interface FormData {
  firstName: string
  lastName: string
  age: number
}

function MyForm() {
  const form = useForm<FormData>({
    defaultValues: {
      firstName: '',
      lastName: '',
      age: 0,
    },
  })

  return (
    <form>
      <form.Field
        name="firstName" // Type-checked against FormData keys
        validators={{
          onChange: ({ value }) => {
            // value is typed as string
            return value.length < 2 ? 'Too short' : undefined
          },
        }}
        children={(field) => (
          <input
            value={field.state.value} // Typed as string
            onChange={(e) => field.handleChange(e.target.value)}
          />
        )}
      />

      <form.Field
        name="age" // Type-checked
        children={(field) => (
          <input
            type="number"
            value={field.state.value} // Typed as number
            onChange={(e) => field.handleChange(Number(e.target.value))}
          />
        )}
      />
    </form>
  )
}

Build docs developers (and LLMs) love