@tanstack/vue-form 1.33.5
Headless, type-safe form state for Vue 3. The package re-exports all of @tanstack/form-core and adds Vue components and hooks (src/index.ts:1-5).
Environment
- Version studied:
@tanstack/vue-form@1.33.5, bundling@tanstack/form-core@1.33.5and@tanstack/vue-store@^0.11.0(package.json:39-42). - Peer dependency:
vue ^3.4.0(package.json:48-50). TypeScript 5.4 through 5.9 are in the test matrix (package.json:54-60). - Import from the package root only. The sole subpath export is
./package.json(package.json:21-33). Dual ESM/CJS,sideEffects: false. - Official docs: https://tanstack.com/form/latest/docs/framework/vue/overview
Minimal form
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'
const form = useForm({
defaultValues: {
fullName: '',
},
onSubmit: async ({ value }) => {
console.log(value)
},
})
</script>
<template>
<form @submit.prevent.stop="form.handleSubmit">
<form.Field name="fullName">
<template v-slot="{ field }">
<input
:name="field.name"
:value="field.state.value"
@blur="field.handleBlur"
@input="(e) => field.handleChange((e.target as HTMLInputElement).value)"
/>
</template>
</form.Field>
<button type="submit">Submit</button>
</form>
</template>
Source: official quick start, https://tanstack.com/form/latest/docs/framework/vue/quick-start
Field binding rules
- Fields are created with the
form.Fieldcomponent. Thenameprop must be a deep key ofdefaultValues, for example'details.email'or`people[${i}].name`. - The scoped slot receives
{ field, state }(src/useField.tsx:543-547).fieldis aFieldApi,statemirrorsfield.state. - Inputs are controlled: bind
:value="field.state.value", push updates throughfield.handleChange, and wire@blur="field.handleBlur". - For number inputs, use
(e.target as HTMLInputElement).valueAsNumber. - Do not write
v-modelon the slot input; state flows throughhandleChange. form.Fieldmerges its props with fallthrough attrs before creating the field (src/useField.tsx:541), so validators and listeners can be passed as props or attrs.
Reactivity
- In
<script setup>, useform.useSelector((state) => state.values.firstName). It returns aReadonly<Ref<T>>(src/useForm.tsx:155-189). form.useStoreis deprecated; it is the same function asuseSelector. Preferform.useSelector(src/useForm.tsx:190-227, 336-337).- In the template, use
<form.Subscribe>with an optionalselectorprop. Withoutselector, the slot receives the whole form state (src/useForm.tsx:338-349). - Do not call the
useFieldhook directly for reactivity. It is designed for use insideform.Field; useform.useSelectorinstead (official basic-concepts note, https://tanstack.com/form/latest/docs/framework/vue/guides/basic-concepts). useSelectoranduseStoreare re-exported from@tanstack/vue-storefor raw store access (src/index.ts:2).
Details: references/reactivity.md
Validation essentials
- Pass validators via the
validatorsprop onform.Field, orvalidatorsinuseFormoptions. Hooks:onMount,onChange,onBlur,onSubmit,onDynamic, each with anAsyncvariant. - A validator returns
undefinedwhen valid, or an error of any type (usually a string). Errors land infield.state.meta.errorsandfield.state.meta.errorMap. - Standard Schema libraries work directly as validators: Zod v3.24.0 or higher, Valibot v1.0.0 or higher, ArkType v2.1.20 or higher, Yup v1.7.0 or higher (official basic-concepts guide).
- Debounce async validators with the
async-debounce-msprop or per-validatoronChangeAsyncDebounceMs. onDynamicvalidators only run whenvalidationLogic: revalidateLogic()is set inuseFormoptions.
<form.Field
name="age"
:validators="{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
}"
>
Details: references/validation.md
Array fields
- Give the wrapping field
mode="array". In array mode the component tracksstate.meta._arrayVersioninstead of the value, so child edits do not re-render the whole list (src/useField.tsx:258-267, src/types.ts:12-15). - Render sub-fields with
v-foroverfield.state.valueand deep names such as`people[${i}].name`. - Mutate through the array methods:
pushValue,removeValue,swapValues,moveValue,insertValue,replaceValue,clearValues.
Details: references/array-fields.md
Form groups
<form.FormGroup name="step1" v-slot="{ group }">builds a sub-form for one key of the form data. The slot receives{ group, state }(src/useFormGroup.tsx:525-529).group.handleSubmit()submits and validates only the group;form.handleSubmit()submits the whole form.- Group validators can return
{ group, fields }, wherefieldskeys are relative to the group, and Standard Schemas can be composed per step.
Details: references/form-groups.md
Version-specific rules
- BREAKING since v1.28.0:
field.state.meta.errorsis flattened by default, so['err']not[['err']]. Restore the old nesting per field withdisableErrorFlat: truein field options. Release: https://github.com/TanStack/form/releases/tag/%40tanstack%2Fvue-form%401.28.0 - DEPRECATED:
form.useStore, useform.useSelector(src/useForm.tsx:190-192, 336-337). - DEPRECATED:
field.getValue(), readfield.state.valueinstead (official FieldApi reference, https://tanstack.com/form/latest/docs/reference/classes/FieldApi). Fieldacceptsmode: 'value' | 'array'. Array mode exists to avoid list re-renders; see https://github.com/TanStack/form/issues/1925 (src/useField.tsx:258-259).useFormgenerates an SSR-safe form id from Vue'suseIdwhen available, falling back to a uuid, so server markup and client hydration agree (src/useForm.tsx:272, src/useFormId.ts:18-24).useFormIditself is internal and not exported (dist/esm/index.js).- Reusable option sets:
formOptions({ defaultValues })from form-core, spread intouseForm.
Pitfalls
- Standard Schema transforms are not preserved.
onSubmitalways receives the input data; callschema.parse(value)insideonSubmitfor transformed output (official submission guide, https://tanstack.com/form/latest/docs/framework/vue/guides/submission-handling). - A form-level validator error on a field can be overwritten by that field's own validator; field validation runs after form-level field errors are set (official validation guide).
- Sync validators run first; the matching
Asyncvalidator is skipped when sync fails, unlessasyncAlways: true. canSubmitis true until the form is touched. Combine!canSubmit || isPristineto gate a submit button before any interaction. Preferaria-disabledoverdisabledfor accessibility (official validation guide).- The
FieldandFormGroupcomponents pass the parent form automatically. Only the standaloneFieldandFormGroupexports require an explicitformoption. - Async default values: wrap them in
reactiveandcomputedrefs (TanStack Query pattern, https://tanstack.com/form/latest/docs/framework/vue/guides/async-initial-values).
References
- references/api-surface.md: exports, hooks, components, and core class methods with source citations.
- references/reactivity.md: selectors, Subscribe, listeners, and reactive meta tracking.
- references/validation.md: validators, schemas, dynamic validation, linked fields, and submission handling.
- references/array-fields.md: array fields and array mutation methods.
- references/form-groups.md: sub-forms, group validation, and group state.