The Field component provides a declarative way to render form fields in Vue applications.
Import
import { Field } from '@tanstack/vue-form'
// or access via form API
const form = useForm(...)
// <form.Field ... />
Props
form
FormApi<TParentData>
required
The parent form API instance.
The field name as a path string (e.g., ‘user.firstName’ or ‘items[0].name’).
The default value for the field.
validators
FieldValidators<TParentData, TName, TData>
Validation functions for the field.validators.onChange
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs on every change.
validators.onChangeAsync
FieldAsyncValidateOrFn<TParentData, TName, TData>
Async validator that runs on change.
validators.onChangeAsyncDebounceMs
Debounce time in milliseconds for async validation.
validators.onBlur
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs when the field loses focus.
validators.onMount
FieldValidateOrFn<TParentData, TName, TData>
Validator that runs when the field is mounted.
Debounce time in milliseconds for async validation. Default is 500ms.
If true, async validation runs even if sync validation fails.
Slot Props
The default slot receives an object with the following properties:
field
FieldApi<TParentData, TName, TData>
The field API instance.Mark the field as touched.
The current field state (same as field.state).Field metadata including errors, touched status, etc.
Usage Example
Basic Field
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'
const form = useForm({
defaultValues: {
firstName: '',
},
onSubmit: async ({ value }) => {
console.log(value)
},
})
</script>
<template>
<form.Field name="firstName">
<template v-slot="{ field, state }">
<label :htmlFor="field.name">First Name:</label>
<input
:id="field.name"
:name="field.name"
:value="field.state.value"
@input="(e) => field.handleChange((e.target as HTMLInputElement).value)"
@blur="field.handleBlur"
/>
</template>
</form.Field>
</template>
Field with Validation
<template>
<form.Field
name="email"
:validators="{
onChange: ({ value }) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return !emailRegex.test(value) ? 'Invalid email address' : undefined
},
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return value.includes('test') ? 'Test emails not allowed' : undefined
},
onChangeAsyncDebounceMs: 500,
}"
>
<template v-slot="{ field, state }">
<label :htmlFor="field.name">Email:</label>
<input
:id="field.name"
:value="field.state.value"
@input="(e) => field.handleChange((e.target as HTMLInputElement).value)"
@blur="field.handleBlur"
/>
<div v-if="state.meta.isTouched && state.meta.errors.length">
<span style="color: red">{{ state.meta.errors[0] }}</span>
</div>
<div v-if="state.meta.isValidating">
<span>Validating...</span>
</div>
</template>
</form.Field>
</template>
Array Field
<template>
<form.Field name="items" mode="array">
<template v-slot="{ field }">
<div v-for="(item, index) in field.state.value" :key="index">
<form.Field :name="`items[${index}].name`">
<template v-slot="{ field: subField }">
<input
:value="subField.state.value"
@input="(e) => subField.handleChange((e.target as HTMLInputElement).value)"
/>
</template>
</form.Field>
<button @click="() => field.removeValue(index)" type="button">
Remove
</button>
</div>
<button @click="() => field.pushValue({ name: '' })" type="button">
Add Item
</button>
</template>
</form.Field>
</template>
See Also
- useForm - Create and manage forms
- useField - Create and manage individual fields
- FieldApi - Core field API documentation