Skills web-forms-vee-validate
VeeValidate v4 patterns - useForm, useField, defineField, useFieldArray, schema validation with Composition API
git clone https://github.com/agents-inc/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/agents-inc/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/src/skills/web-forms-vee-validate" ~/.claude/skills/agents-inc-skills-web-forms-vee-validate-2efded && rm -rf "$T"
src/skills/web-forms-vee-validate/SKILL.mdVeeValidate Form Validation Patterns
Quick Guide: Use VeeValidate v4 for Vue 3 form validation with Composition API. Use
for form state,useFormfor quick field setup,defineFieldfor custom input components, anduseFieldfor dynamic lists. Always wrap schema libraries withuseFieldArray. Always usetoTypedSchema()(not index) as iteration key in field arrays.field.key
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use
wrapper when using schema libraries in v4 - raw schemas won't work)toTypedSchema()
(You MUST use
as iteration key in useFieldArray - NEVER use array index)field.key
(You MUST use function form
or () => props.name
in useField for prop reactivity)toRef()
(You MUST initialize field array values in
- undefined arrays cause errors)initialValues
</critical_requirements>
Auto-detection: VeeValidate, vee-validate, useForm, useField, defineField, useFieldArray, toTypedSchema, ErrorMessage, Form component
When to use:
- Building Vue 3 forms with validation requirements
- Managing complex form state with multiple fields
- Creating dynamic forms with add/remove field capabilities
- Integrating schema validation libraries with
toTypedSchema() - Building multi-step wizard forms
When NOT to use:
- Single input without validation (use native v-model)
- Server-only forms with server actions (use native form submission)
- Read-only data display (not a form scenario)
Detailed Resources:
- examples/core.md - defineField, useField, form meta, eager validation
- examples/validation.md - Zod/Yup/Valibot schema integration, conditional validation
- examples/arrays.md - useFieldArray, nested arrays, reordering
- reference.md - Decision frameworks, API reference tables, anti-patterns
<philosophy>
Philosophy
VeeValidate v4 embraces Vue 3's Composition API as the primary approach, enabling seamless integration with any UI library. Validation logic is decoupled from presentation, allowing schema-first validation with full TypeScript inference.
Core Principles:
- Composition API first - Use
,useForm
,useField
for seamless Vue 3 integrationdefineField - Schema-first validation - Prefer declarative schemas over inline rules
- Full type safety - TypeScript inference from schemas and generics
- UI agnostic - Works with any component library or native inputs
- Minimal re-renders - Efficient reactivity through Vue's reactive system
defineField vs useField:
| Feature | | |
|---|---|---|
| Use case | Quick form setup with native inputs | Building reusable custom input components |
| Form context | Always requires form context | Optional form integration |
| Best for | Application-level forms | Component library development |
<patterns>
Core Patterns
Pattern 1: Basic Form with defineField
Use
useForm with defineField for the fastest form setup. defineField returns a [model, attrs] tuple for v-model binding. See examples/core.md for full examples.
<script setup lang="ts"> import { useForm } from "vee-validate"; import { toTypedSchema } from "@vee-validate/zod"; import { z } from "zod"; const schema = toTypedSchema( z.object({ email: z.string().email("Invalid email"), password: z.string().min(8, "At least 8 characters"), }), ); const { handleSubmit, errors, defineField } = useForm({ validationSchema: schema, }); const [email, emailAttrs] = defineField("email"); const onSubmit = handleSubmit((values) => { // values is fully typed from schema }); </script>
Pattern 2: Custom Input Components with useField
Use
useField when building reusable input components. Critical: use function form () => props.name to maintain reactivity. See examples/core.md for full component example.
<script setup lang="ts"> import { useField } from "vee-validate"; const props = defineProps<{ name: string }>(); // CRITICAL: Function form maintains reactivity const { value, errorMessage, handleBlur, meta } = useField<string>( () => props.name, undefined, { validateOnValueUpdate: false }, ); </script>
Pattern 3: Schema Validation with toTypedSchema
Always wrap schema libraries with
toTypedSchema(). Initialize ALL fields used in refine/superRefine - Zod skips refinements when keys are undefined. See examples/validation.md for Zod, Yup, and Valibot examples.
import { toTypedSchema } from "@vee-validate/zod"; // CORRECT: Wrapped schema const schema = toTypedSchema(z.object({ email: z.string().email() })); // WRONG: Raw schema won't work with VeeValidate const schema = z.object({ email: z.string().email() });
Pattern 4: Dynamic Arrays with useFieldArray
Use
useFieldArray for add/remove/reorder patterns. Always use field.key as :key, never array index. Initialize arrays in initialValues. See examples/arrays.md for full patterns.
<script setup lang="ts"> import { useForm, useFieldArray } from "vee-validate"; const { handleSubmit } = useForm({ initialValues: { users: [{ name: "", email: "" }] }, }); const { fields, push, remove } = useFieldArray("users"); </script> <template> <!-- CORRECT: field.key as key --> <div v-for="(field, index) in fields" :key="field.key"> <input v-model="field.value.name" /> </div> </template>
Pattern 5: Server-Side Error Handling
Set errors from API responses using
setErrors (multiple) or setFieldError (single).
const { handleSubmit, setErrors, setFieldError } = useForm({ ... }); const onSubmit = handleSubmit(async (values) => { try { await api.createUser(values); } catch (error) { if (error.response?.data?.errors) { // Set multiple field errors from API setErrors(mapApiErrors(error.response.data.errors)); } else { setFieldError("apiError", "Something went wrong"); } } });
Pattern 6: Form Meta and State
Access aggregated form state for UX features like dirty tracking, submit button state, and reset. See examples/core.md for full example.
</patterns><script setup lang="ts"> const { handleSubmit, meta, isSubmitting, resetForm } = useForm({ ... }); </script> <template> <button :disabled="!meta.valid || !meta.dirty || isSubmitting"> {{ isSubmitting ? "Saving..." : "Save" }} </button> <p v-if="meta.dirty">You have unsaved changes</p> </template>
<red_flags>
RED FLAGS
High Priority Issues:
- Missing
wrapper - raw schemas silently fail to validatetoTypedSchema() - Using array index as
in:key
- causes form state corruption on add/removeuseFieldArray - Direct
inprops.name
- loses reactivity when prop changesuseField - Undefined
for field arrays - causes runtime errorsinitialValues
Medium Priority Issues:
enabled everywhere - validates on every keystroke (noisy UX)validateOnValueUpdate- Not handling async validation errors - API failures need
orsetErrors()setFieldError() - Forgetting
after submission - form stays dirty after successresetForm() - Multiple
calls in same component - creates conflicting form contextsuseForm - Not using
for error display - shows errors before user interactionmeta.touched
Gotchas & Edge Cases:
has first error per field;errors
has ALL errors per field as arrayserrorBag
may be false during initial render before validation runsmeta.valid- Nested fields use dot notation:
defineField('user.profile.name') - Array field errors use bracket notation:
errors['items[0].name']
notresetForm({ values: data })
- wrong structure silently failsresetForm(data)
(default) drops values of unmounted fields - setkeepValuesOnUnmount: false
for multi-step formstrue- Mixing
component with<Form>
creates conflicting contexts - pick one approachuseForm() - Zod
do NOT execute when object keys are missing - always initialize all fieldsrefine/superRefine
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST use
wrapper when using schema libraries in v4 - raw schemas won't work)toTypedSchema()
(You MUST use
as iteration key in useFieldArray - NEVER use array index)field.key
(You MUST use function form
or () => props.name
in useField for prop reactivity)toRef()
(You MUST initialize field array values in
- undefined arrays cause errors)initialValues
Failure to follow these rules will break form validation, cause reactivity issues, and corrupt form state.
</critical_reminders>