dist/plugins/web-forms-vee-validate/skills/web-forms-vee-validate/SKILL.md
VeeValidate v4 patterns - useForm, useField, defineField, useFieldArray, schema validation with Composition API
npx skillsauth add agents-inc/skills web-forms-vee-validateInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use VeeValidate v4 for Vue 3 form validation with Composition API. Use
useFormfor form state,defineFieldfor quick field setup,useFieldfor custom input components, anduseFieldArrayfor dynamic lists. Always wrap schema libraries withtoTypedSchema(). Always usefield.key(not index) as iteration key in field arrays.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use toTypedSchema() wrapper when using schema libraries in v4 - raw schemas won't work)
(You MUST use field.key as iteration key in useFieldArray - NEVER use array index)
(You MUST use function form () => props.name or toRef() in useField for prop reactivity)
(You MUST initialize field array values in initialValues - undefined arrays cause errors)
</critical_requirements>
Auto-detection: VeeValidate, vee-validate, useForm, useField, defineField, useFieldArray, toTypedSchema, ErrorMessage, Form component
When to use:
toTypedSchema()When NOT to use:
Detailed Resources:
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:
useForm, useField, defineField for seamless Vue 3 integrationdefineField vs useField:
| Feature | defineField | useField |
| ---------------- | ----------------------------------- | ----------------------------------------- |
| 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 |
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>
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>
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() });
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>
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");
}
}
});
Access aggregated form state for UX features like dirty tracking, submit button state, and reset. See examples/core.md for full example.
<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>
</patterns>
<red_flags>
High Priority Issues:
toTypedSchema() wrapper - raw schemas silently fail to validate:key in useFieldArray - causes form state corruption on add/removeprops.name in useField - loses reactivity when prop changesinitialValues for field arrays - causes runtime errorsMedium Priority Issues:
validateOnValueUpdate enabled everywhere - validates on every keystroke (noisy UX)setErrors() or setFieldError()resetForm() after submission - form stays dirty after successuseForm calls in same component - creates conflicting form contextsmeta.touched for error display - shows errors before user interactionGotchas & Edge Cases:
errors has first error per field; errorBag has ALL errors per field as arraysmeta.valid may be false during initial render before validation runsdefineField('user.profile.name')errors['items[0].name']resetForm({ values: data }) not resetForm(data) - wrong structure silently failskeepValuesOnUnmount: false (default) drops values of unmounted fields - set true for multi-step forms<Form> component with useForm() creates conflicting contexts - pick one approachrefine/superRefine do NOT execute when object keys are missing - always initialize all fields</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST use toTypedSchema() wrapper when using schema libraries in v4 - raw schemas won't work)
(You MUST use field.key as iteration key in useFieldArray - NEVER use array index)
(You MUST use function form () => props.name or toRef() in useField for prop reactivity)
(You MUST initialize field array values in initialValues - undefined arrays cause errors)
Failure to follow these rules will break form validation, cause reactivity issues, and corrupt form state.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety