---
name: sdlc-vue-forms
description: |
Vue 3 form patterns: native v-model, vee-validate + zod (most common), VueUse helpers, defineModel for custom inputs, controlled vs uncontrolled, field arrays, multi-step wizards.
Use this skill to:
- Wire vee-validate with a validation schema (zod / yup).
- Build custom form components with defineModel (Vue 3.4+).
- Implement multi-step forms.
- Handle async validation.
- Integrate forms with TanStack Query mutations or Pinia actions.
Do NOT use this skill for:
- General SFC conventions (see vue-conventions).
- State management (see vue-state-management).
- Routing (see vue-routing).
- Testing forms (see vue-testing).
paths: ["src/**/*.vue"]
---
# Vue 3 Form Patterns
`v-model` is the foundation. Add vee-validate when validation grows beyond simple HTML5 attributes.
## Detection
| Marker (in deps) | Library |
|---|---|
| `vee-validate` (+ `@vee-validate/zod` or `@vee-validate/yup`) | vee-validate (recommended) |
| `@vueuse/core` | VueUse helpers (lighter alternative) |
| (none) | Native v-model + HTML5 validation |
| `zod` / `yup` / `valibot` | Pair with vee-validate via resolver |
## Native v-model (sufficient for simple forms)
```vue
```
Built-in HTML5 validation (`required`, `type="email"`, `pattern`, `minlength`, `maxlength`) is free. Use it for forms ≤3 fields with simple rules.
### v-model modifiers
```vue
```
### v-model on custom components
In Vue 3.4+, use `defineModel`:
```vue
{{ error }}
```
```vue
```
For Vue 3.0–3.3, use manual props + emits:
```vue
```
## vee-validate (recommended for non-trivial forms)
```bash
pnpm add vee-validate @vee-validate/zod zod
```
### Basic form
```vue
```
### Field components alternative
```vue
```
`` and `` are convenient but less flexible. Pick one approach per project.
### Field arrays
```vue
```
`field.key` is a stable key from vee-validate — DON'T use index.
### Async validation
```ts
const schema = z.object({
username: z.string().min(3).refine(
async (u) => {
const res = await fetch(`/api/users/check?u=${u}`);
return (await res.json()).available;
},
'Username taken'
),
});
const { defineField } = useForm({
validationSchema: toTypedSchema(schema),
validateOnInput: false,
validateOnBlur: true, // validate on blur — appropriate for expensive checks
});
```
### Server error handling
```ts
const { handleSubmit, setErrors, setFieldError } = useForm({...});
const onSubmit = handleSubmit(async (values) => {
try {
await createUser(values);
} catch (err) {
if (err instanceof FetchError && err.status === 409) {
setFieldError('email', 'Email already in use');
} else {
setErrors({ form: 'Something went wrong' });
}
}
});
```
### Multi-step forms
```vue
```
## VueUse useForm (lighter alternative)
`@vueuse/core` doesn't have a direct `useForm` equivalent. For simple forms, native v-model + manual validation is enough. For complex forms, use vee-validate.
VueUse useful form-related composables:
- `useDebouncedRef` — debounce input value.
- `useStorage` — persist form state to localStorage (NEVER for sensitive data).
- `onClickOutside` — close dropdowns / pickers.
- `useFocus` — focus management for accessibility.
## Integration with TanStack Query / Pinia
```vue
```
## Accessibility checklist
- Every input has a `