Form Setup
Learn how to properly configure useForm for your needs.
Basic Setup
The simplest form setup requires only a schema:
import { useForm } from '@vuehookform/core'
import { z } from 'zod'
const schema = z.object({
name: z.string().min(1, 'Name is required'),
})
const { register, handleSubmit } = useForm({ schema })Configuration Options
Schema
The Zod schema that defines your form structure and validation rules:
const schema = z.object({
email: z.email(),
age: z.number().min(18),
preferences: z.object({
newsletter: z.boolean(),
}),
})
const form = useForm({ schema })Default Values
Pre-populate form fields with initial values:
const form = useForm({
schema,
defaultValues: {
email: 'user@example.com',
age: 25,
preferences: {
newsletter: true,
},
},
})TIP
Default values should match your schema structure. TypeScript will warn you if they don't.
Async Default Values
Load initial values from an API:
const { formState } = useForm({
schema,
defaultValues: async () => {
const response = await fetch('/api/user')
return response.json()
},
onDefaultValuesError: (error) => {
console.error('Failed to load:', error)
},
})Track loading state via formState:
formState.value.isLoading-truewhile fetchingformState.value.isReady-trueonce loading completesformState.value.defaultValuesError- error object if loading failed
See Async Patterns for complete examples.
Validation Mode
Control when validation occurs:
const form = useForm({
schema,
mode: 'onBlur', // 'onSubmit' | 'onBlur' | 'onChange' | 'onTouched'
})See Validation Modes for detailed descriptions of each mode.
Disabled State
Disable the entire form:
import { ref } from 'vue'
const isLoading = ref(false)
const form = useForm({
schema,
disabled: isLoading, // Reactive ref
})When disabled:
- All inputs receive the
disabledattribute - Form submission is blocked
- Useful for loading states
Native Validation
Enable browser's native validation UI:
const form = useForm({
schema,
shouldUseNativeValidation: true,
})This adds HTML5 validation attributes and enables :valid/:invalid CSS selectors.
Should Unregister
Control whether field data is removed when a field is unmounted:
const form = useForm({
schema,
shouldUnregister: true, // Remove data when fields unmount (default: false)
})When shouldUnregister: true:
- Field values are deleted when the field unmounts
- Useful for dynamic forms where fields are conditionally shown
- Override per-field with
register('field', { shouldUnregister: false })
When shouldUnregister: false (default):
- Field values persist even when unmounted
- Form retains all values for submission
- Better for multi-step forms
External Values Sync
Sync form values with external state without marking fields as dirty:
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const externalValues = computed(() => ({
search: route.query.q || '',
page: Number(route.query.page) || 1,
}))
const form = useForm({
schema,
values: externalValues, // Form syncs when this changes
})Use values when you need to sync with URL params, stores, or parent state.
External Errors
Pass server-side validation errors into the form:
import { ref } from 'vue'
const serverErrors = ref({})
const form = useForm({
schema,
errors: serverErrors, // Merged with validation errors
})
// After API returns validation errors:
serverErrors.value = {
email: 'This email is already registered',
username: 'Username contains invalid characters',
}Server errors take precedence over client-side validation errors.
See Async Patterns for complete server error handling examples.
Return Values
useForm returns an object with everything you need:
const {
// Register inputs
register,
unregister,
// Handle form submission
handleSubmit,
// Reactive form state
formState,
// Field array management
fields,
// Programmatic control
setValue,
getValue,
getValues,
getFieldState,
reset,
resetField,
// Validation
trigger, // Manually trigger validation
// Error management
setError,
setErrors,
clearErrors,
hasErrors,
getErrors,
// Watch field values
watch,
// Focus management
setFocus,
// For child components
control,
} = useForm({ schema })See Programmatic Control for detailed usage of each method.
Complete Example
<script setup lang="ts">
import { ref } from 'vue'
import { useForm } from '@vuehookform/core'
import { z } from 'zod'
const schema = z.object({
firstName: z.string().min(1, 'First name is required'),
lastName: z.string().min(1, 'Last name is required'),
email: z.email('Invalid email address'),
age: z.number().min(18, 'Must be 18 or older'),
})
const isLoading = ref(false)
const { register, handleSubmit, formState, reset } = useForm({
schema,
mode: 'onBlur',
defaultValues: {
firstName: '',
lastName: '',
email: '',
age: 18,
},
disabled: isLoading,
})
const onSubmit = async (data: z.infer<typeof schema>) => {
isLoading.value = true
try {
await submitToServer(data)
reset() // Clear form on success
} finally {
isLoading.value = false
}
}
</script>
<template>
<form @submit="handleSubmit(onSubmit)">
<!-- Form fields here -->
</form>
</template>Next Steps
- Learn about Validation strategies
- Understand Error Handling
- Explore Form State
