Using with native React state
Connect StackForm to plain React state via NativeFormProvider
Install
npm install @stackform/core @stackform/ui @stackform/nativeNo 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 errorErrors 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.