Form
Usage
Use the Form component to validate form data using schema libraries such as Yup, Zod, Joi, Valibot, or your own validation logic.
It works with the FormGroup component to display error messages around form elements automatically.
The form component requires two props:
state
- a reactive object holding the form's state.schema
- a schema object from a validation library like Yup, Zod, Joi or Valibot.
<script setup lang="ts">
import { object, string, type InferType } from 'yup'
import type { FormSubmitEvent } from '#ui/types'
const schema = object({
email: string().email('Invalid email').required('Required'),
password: string()
.min(8, 'Must be at least 8 characters')
.required('Required')
})
type Schema = InferType<typeof schema>
const state = reactive({
email: undefined,
password: undefined
})
async function onSubmit (event: FormSubmitEvent<Schema>) {
// Do something with event.data
console.log(event.data)
}
</script>
<template>
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<UFormGroup label="Email" name="email">
<UInput v-model="state.email" />
</UFormGroup>
<UFormGroup label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormGroup>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
Custom validation
Use the validate
prop to apply your own validation logic.
The validation function must return a list of errors with the following attributes:
message
- Error message for display.path
- Path to the form element corresponding to thename
attribute.
schema
prop to handle complex use cases.<script setup lang="ts">
import type { FormError, FormSubmitEvent } from '#ui/types'
const state = reactive({
email: undefined,
password: undefined
})
const validate = (state: any): FormError[] => {
const errors = []
if (!state.email) errors.push({ path: 'email', message: 'Required' })
if (!state.password) errors.push({ path: 'password', message: 'Required' })
return errors
}
async function onSubmit (event: FormSubmitEvent<any>) {
// Do something with data
console.log(event.data)
}
</script>
<template>
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
<UFormGroup label="Email" name="email">
<UInput v-model="state.email" />
</UFormGroup>
<UFormGroup label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormGroup>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
This can also be used to integrate with other validation libraries. Here is an example with Vuelidate:
<script setup lang="ts">
import useVuelidate from '@vuelidate/core'
const props = defineProps({
rules: { type: Object, required: true },
model: { type: Object, required: true }
})
const form = ref();
const v = useVuelidate(props.rules, props.model)
async function validateWithVuelidate() {
v.value.$touch()
await v.value.$validate()
return v.value.$errors.map((error) => ({
message: error.$message,
path: error.$propertyPath,
}))
}
defineExpose({
validate: async () => {
await form.value.validate()
}
})
</script>
<template>
<UForm ref="form" :state="model" :validate="validateWithVuelidate">
<slot />
</UForm>
</template>
Backend validation
You can manually set errors after form submission if required. To do this, simply use the form.setErrors
function to set the errors as needed.
<script setup lang="ts">
import type { Form, FormSubmitEvent } from '#ui/types'
interface Schema {
email?: string
password?: string
}
const state = reactive<Schema>({
email: undefined,
password: undefined
})
const form = ref<Form<Schema>>()
async function onSubmit (event: FormSubmitEvent<Schema>) {
if (!form.value) {
console.error("Form component not mounted yet")
return
}
form.value.clear()
try {
const response = await $fetch('...')
// ...
} catch (err) {
if (err.statusCode === 422) {
form.value.setErrors(err.data.errors.map((err) => ({
// Map validation errors to { path: string, message: string }
message: err.message,
path: err.path,
})))
}
}
}
</script>
<template>
<UForm ref="form" :state="state" @submit="onSubmit">
<UFormGroup label="Email" name="email">
<UInput v-model="state.email" />
</UFormGroup>
<UFormGroup label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormGroup>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
Input events
The Form component automatically triggers validation upon submit
, input
, blur
or change
events.
This ensures that any errors are displayed as soon as the user interacts with the form elements. You can control when validation happens this using the validate-on
prop.
input
event is not triggered until after the initial blur
event. This is to prevent the form from being validated as the user is typing. You can override this behavior by setting the eager-validation
prop on FormGroup
to true
.Error event
You can listen to the @error
event to handle errors. This event is triggered when the form is submitted and contains an array of FormError
objects with the following fields:
id
- the identifier of the form element.path
- the path to the form element matching thename
.message
- the error message to display.
Here's an example that focuses the first input element with an error after the form is submitted:
<script setup lang="ts">
import type { FormError, FormErrorEvent, FormSubmitEvent } from '#ui/types'
const state = reactive({
email: undefined,
password: undefined
})
const validate = (state: any): FormError[] => {
const errors = []
if (!state.email) errors.push({ path: 'email', message: 'Required' })
if (!state.password) errors.push({ path: 'password', message: 'Required' })
return errors
}
async function onSubmit (event: FormSubmitEvent<any>) {
// Do something with data
console.log(event.data)
}
async function onError (event: FormErrorEvent) {
const element = document.getElementById(event.errors[0].id)
element?.focus()
element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
</script>
<template>
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit" @error="onError">
<UFormGroup label="Email" name="email">
<UInput v-model="state.email" />
</UFormGroup>
<UFormGroup label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormGroup>
<UButton type="submit">
Submit
</UButton>
</UForm>
</template>
Props
undefined
[]
["blur", "input", "change", "submit"]
API
When accessing the component via a template ref, you can use the following:
Triggers form submission.
Triggers form validation. Will raise any errors unless opts.silent
is set to true.
Clears form errors associated with a specific path. If no path is provided, clears all form errors.
Retrieves form errors associated with a specific path. If no path is provided, returns all form errors.
Sets form errors for a given path. If no path is provided, overrides all errors.
A reference to the array containing validation errors. Use this to access or manipulate the error information.