web-forms-vee-validate-a7ca06 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-forms-vee-validate-a7ca06 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide:useFormowns the form state;defineFieldreturns a[model, attrs]tuple tov-modelonto a native input, anduseFieldbinds a field inside a reusable input component — with its name passed as a getter so it stays reactive. A schema reachesvalidationSchemathroughtoTypedSchema(), which is also what supplies the types.useFieldArraydrives repeatable groups and iterates onfield.key. Everything here is v4; v5 removes the adapter, so check reference.md before targeting it.
Detailed Resources:
defineField, inline rules, a reusable input built on useField, form meta, eager validationtoTypedSchema, conditional schemas, all errors per fielduseFieldArray, nested arrays, reorderinguseForm plus defineField, andv-model on the returned model. Follow examples/core.md.
useField inside the component, taking its name as() => props.name. Follow examples/core.md Pattern 3.
rather than a companion to it. Each creates its own form context, so a component that calls useForm and also renders <Form> has two, and the fields register with whichever they are nested in.
<critical_requirements>
Wrap a schema in `toTypedSchema()` before handing it to `validationSchema`. The adapter converts the schema's result into the shape the form reads and is what supplies the value types; a raw schema is accepted without complaint and then validates nothing.
Pass `useField` its name as `() => props.name` or `toRef(props, "name")`. A plain props.name is read once at setup, so the field keeps binding to the name the component first mounted with.
Give `initialValues` an entry for every field, arrays included. Field arrays iterate their value and fail on undefined, and a cross-field rule is skipped entirely when one of the keys it compares is missing.
Iterate `useFieldArray` on `field.key`. It is the identity VeeValidate assigns each entry and it survives insertion and reordering, which a positional index does not.
</critical_requirements>
Auto-detection: vee-validate, @vee-validate/zod, @vee-validate/yup, @vee-validate/valibot, useForm, useField, defineField, useFieldArray, toTypedSchema, validationSchema, errorBag, handleSubmit, resetForm, setFieldError, setErrors, validateOnValueUpdate, keepValuesOnUnmount, ErrorMessage, useFormContext
Applies to:
Handled elsewhere:
toTypedSchema adapts whatever schema it is given, and how thatschema states its rules is settled by whatever owns it.
skill.
initialValues is a plain object the form does not fetch.<philosophy>
Validation is declared once, at the form, and read per field. useForm holds the schema and the values; each field asks the form for its own slice, so a field knows its error without knowing the rule that produced it. That is what lets an input component be written without knowing which form it will be dropped into — it takes a name and binds itself.
Reactivity is the thing to keep intact. Everything a field receives from the form is a ref or a getter, and the common failure is flattening one of them: reading props.name instead of passing a getter, or unwrapping a computed schema with .value before the form can track it.
</philosophy>
<decision_framework>
defineField | useField | |
|---|---|---|
| Returns | [model, attrs] for v-model and v-bind | value, errorMessage, meta, handlers |
| Form context | Required — it comes off a useForm result | Optional; falls back to standalone validation |
| Best for | The form's own template, native inputs | A component that binds itself by name |
defineField where the form and the inputs are in one component. useField where the input is a component in its own right, or where the binding needs handlers rather than a v-model.
</decision_framework>
<patterns>
defineField returns a tuple: the model for v-model, and the attrs carrying the event handlers that drive validation timing.
<script setup lang="ts">
const { handleSubmit, errors, defineField } = useForm({
validationSchema: schema,
initialValues: { email: "", password: "" },
});
const [email, emailAttrs] = defineField("email");
const onSubmit = handleSubmit(async (values) => {
await login(values);
});
</script>
<template>
<input v-model="email" v-bind="emailAttrs" />
<span v-if="errors.email" role="alert">{{ errors.email }}</span>
</template>Dropping v-bind="emailAttrs" leaves the model bound but the handlers unattached, so the field never blurs and never validates.
Full code: examples/core.md Pattern 1
The name arrives as a getter so the field re-binds if the prop changes.
<script setup lang="ts">
const props = defineProps<{ name: string }>();
const { value, errorMessage, handleBlur, handleChange, meta } =
useField<string>(() => props.name, undefined, {
validateOnValueUpdate: false,
});
</script>validateOnValueUpdate: false holds validation back to blur, which is what stops errors appearing while the user is still typing the first character.
Full code: examples/core.md Pattern 3
The adapter is the boundary: the schema states the rules, and toTypedSchema renders them as VeeValidate errors and as the type of values.
import { toTypedSchema } from "@vee-validate/zod";
const schema = toTypedSchema(registrationSchema);
const { handleSubmit, errors } = useForm({
validationSchema: schema,
initialValues: { email: "", password: "", confirmPassword: "" },
});Every key a cross-field rule compares needs an entry in initialValues — a rule reading a key that is undefined is skipped rather than failed, so the form submits as valid.
Full code: examples/validation.md
fields carries a key and a value per entry. The key is the iteration key; the value is what the inputs bind to.
<script setup lang="ts">
const { handleSubmit } = useForm({
initialValues: { users: [{ name: "", email: "" }] },
});
const { fields, push, remove } = useFieldArray("users");
</script>
<template>
<div v-for="(field, index) in fields" :key="field.key">
<input v-model="field.value.name" />
<button type="button" @click="remove(index)">Remove</button>
</div>
</template>Full code: examples/arrays.md
setErrors takes a record of field names to messages, and setFieldError sets one. Both put a server's verdict where the field's own error would go, so the template needs no separate branch.
const onSubmit = handleSubmit(async (values) => {
try {
await createUser(values);
} catch (error) {
const fieldErrors = extractFieldErrors(error);
if (fieldErrors) {
setErrors(fieldErrors);
} else {
setFieldError("email", "Could not create the account");
}
}
});The next validation run clears them — a server error persists only until the field it names changes, which is why a rejected submission needs the message re-set rather than remembered.
meta aggregates the fields: valid, dirty, touched and pending describe the form as a whole.
<template>
<button :disabled="!meta.valid || !meta.dirty || isSubmitting">
{{ isSubmitting ? "Saving..." : "Save" }}
</button>
<p v-if="meta.dirty">You have unsaved changes</p>
</template>After a successful save, resetForm({ values: saved }) makes the saved data the new baseline, which is what returns meta.dirty to false.
Full code: examples/core.md Pattern 4
</patterns>
<red_flags>
Breaks at runtime:
validationSchema without toTypedSchema() — it is accepted and then nevervalidates, so the form submits whatever it holds.
props.name passed to useField instead of a getter — the field binds to the name captured atsetup and ignores every later change.
initialValues missing a field array — the composable iterates undefined and throws.useFieldArray iteration key — Vue matches the wrong entries, so removinga middle row shifts every value below it up one.
resetForm(data) in place of resetForm({ values: data }) — the argument shape is wrong, and thereset takes no effect rather than reporting.
Surprising behaviour:
errors holds the first error per field; errorBag holds all of them as arrays. A password ruleset showing one unmet requirement at a time is reading the wrong one.
meta.valid is false on the first render, before validation has run — a submit button gated on italone starts disabled.
keepValuesOnUnmount defaults to false, so a field that unmounts loses its value. Set it true foranything that hides fields between steps.
defineField("user.profile.name")) and array items bybrackets (errors["items[0].name"]) — the two are not interchangeable.
.value — unwrapped, the form bindsto one snapshot and stops tracking it.
validateOnValueUpdate left on validates every keystroke, including the first.meta.touched appear before the user has reached the field.Worked before/after code for the most common of these is in reference.md.
</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.