StackForm
Getting Started

Using with TanStack Form

Connect StackForm to TanStack Form via TanstackFormProvider

Install

npm install @stackform/core @stackform/ui @stackform/tanstack @tanstack/react-form

Requires TanStack Form v1.


Quick start

A complete form with three fields, error display, and submission:

import { useForm } from '@tanstack/react-form'
import { StackFormProvider, TextField, CheckboxField } from '@stackform/ui'
import { TanstackFormProvider } from '@stackform/tanstack'

export function ContactForm() {
  const form = useForm({
    defaultValues: { name: '', email: '', terms: false },
    onSubmit: async ({ value }) => {
      console.log('Submitted:', value)
    },
  })

  return (
    <StackFormProvider>
      <TanstackFormProvider form={form}>
        <form
          onSubmit={(e) => {
            e.preventDefault()
            e.stopPropagation()
            form.handleSubmit()
          }}
          className="space-y-4"
        >
          <TextField name="name" label="Name" placeholder="Your name" />
          <TextField
            name="email"
            label="Email"
            type="email"
            placeholder="you@example.com"
            hint="We'll never share your email."
          />
          <CheckboxField name="terms" label="I agree to the terms and conditions" />
          <button type="submit" disabled={form.state.isSubmitting}>
            {form.state.isSubmitting ? 'Submitting…' : 'Submit'}
          </button>
        </form>
      </TanstackFormProvider>
    </StackFormProvider>
  )
}

Provider setup

Pass your TanStack form instance directly to TanstackFormProvider. The provider subscribes to the form store and injects the resolver and form state into StackForm context:

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

const form = useForm({ defaultValues: { name: '' } })

<TanstackFormProvider form={form}>
  {/* field components here */}
</TanstackFormProvider>

StackFormProvider must wrap TanstackFormProvider. The form prop accepts any TanStack AnyFormApi — the provider is not tied to a specific schema library.


Error display

Errors surface automatically whenever TanStack Form sets them. Configure validation in your useForm call or via per-field validators — StackForm displays whatever the form library sets, with no additional wiring.

const form = useForm({
  defaultValues: { email: '' },
  validators: {
    onChange: ({ value }) => {
      if (!value.email.includes('@')) {
        return { fields: { email: 'Enter a valid email address' } }
      }
    },
  },
})

// TextField renders the error automatically when validation fails
<TextField name="email" label="Email" />

TanStack Form validates on onChange by default. To validate only on submit, set validationTrigger: 'submit' in your useForm options.


Form submission

Call form.handleSubmit() inside your <form> element's onSubmit. Both preventDefault and stopPropagation are needed when forms are nested. The onSubmit callback defined in useForm receives the validated values.

const form = useForm({
  defaultValues: { name: '', email: '' },
  onSubmit: async ({ value }) => {
    await saveToApi(value)
  },
})

<form
  onSubmit={(e) => {
    e.preventDefault()
    e.stopPropagation()
    form.handleSubmit()
  }}
>
  {/* fields */}
  <button type="submit" disabled={form.state.isSubmitting}>
    {form.state.isSubmitting ? 'Saving…' : 'Save'}
  </button>
</form>

Validation differences vs React Hook Form

BehaviourReact Hook FormTanStack Form
Default validation triggersubmitonChange
Per-field validatorsvia register rules or resolvervia validators on the field
Async validationresolverasyncValidators on the field
Submission handlerhandleSubmit(fn) on <form>onSubmit in useForm options

Configure validation timing in your TanStack useForm call or per-field validators option — not in StackForm.