StackForm
Getting Started

Using with native React state

Connect StackForm to plain React state via NativeFormProvider

Install

npm install @stackform/core @stackform/ui @stackform/native

No external form library required.


Quick start

A complete form using only React state:

import { StackFormProvider, TextField } from '@stackform/ui'
import { NativeFormProvider, useNativeForm } from '@stackform/native'

interface FormValues {
  name: string
  email: string
}

export function ContactForm() {
  const form = useNativeForm<FormValues>({ name: '', email: '' })

  const onSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    form.handleSubmit((values) => {
      console.log('Submitted:', values)
    })
  }

  return (
    <StackFormProvider>
      <NativeFormProvider form={form}>
        <form onSubmit={onSubmit} className="space-y-4">
          <TextField name="name" label="Name" placeholder="Your name" />
          <TextField
            name="email"
            label="Email"
            type="email"
            placeholder="you@example.com"
          />
          <button type="submit" disabled={form.isSubmitting}>
            {form.isSubmitting ? 'Submitting…' : 'Submit'}
          </button>
        </form>
      </NativeFormProvider>
    </StackFormProvider>
  )
}

Provider setup

useNativeForm initialises form state with your default values and returns a NativeFormHandle. Pass it to NativeFormProvider, which injects it into StackForm context.

import { NativeFormProvider, useNativeForm } from '@stackform/native'

const form = useNativeForm({ name: '', email: '' })

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

StackFormProvider must wrap NativeFormProvider.


Error display

The native adapter does not include a built-in validation runner. Use the validate prop to attach a field-level validator — it runs on blur and returns an error string or undefined:

<TextField
  name="email"
  label="Email"
  validate={(value) => {
    if (!value.includes('@')) return 'Enter a valid email address'
  }}
/>

To set or clear errors imperatively — for example from an async check — use form.setFieldError:

form.setFieldError('email', 'This email is already registered')
form.setFieldError('email', undefined) // clears the error

Errors surface automatically in the field's error slot — no additional prop wiring needed.


Form submission

form.handleSubmit takes a callback that receives the current field values. It sets form.isSubmitting to true for the duration of the callback and resolves once it completes or throws.

const onSubmit = (e: React.FormEvent) => {
  e.preventDefault()
  form.handleSubmit(async (values) => {
    await saveToApi(values)
  })
}

<form onSubmit={onSubmit}>
  {/* fields */}
  <button type="submit" disabled={form.isSubmitting}>
    {form.isSubmitting ? 'Saving…' : 'Save'}
  </button>
</form>

To reset the form after submission, call form.reset().


When to use the native adapter

The native adapter manages form state with plain useState — no external form library required. Use it when:

  • Prototyping a form quickly without committing to RHF or TanStack
  • The form is simple enough that a full form library adds unnecessary overhead
  • You need to integrate StackForm into a codebase that already manages state manually

For production forms with complex validation, async submission, or field arrays, prefer the RHF or TanStack adapters.