本文へ移動
cccskills
無料GitHub で公開

web-forms-vee-validate

VeeValidate v4 patterns - useForm, useField, defineField, useFieldArray, schema validation with Composition API

インストール方法を見る

含まれるファイル(6)

  • SKILL.md11.0 KB
  • examples/arrays.md15.0 KB
  • examples/core.md11.9 KB
  • examples/validation.md9.0 KB
  • metadata.yaml347 B
  • reference.md12.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

VeeValidate Form Validation Patterns

Quick Guide: useForm owns the form state; defineField returns a [model, attrs] tuple to v-model onto a native input, and useField binds a field inside a reusable input component — with its name passed as a getter so it stays reactive. A schema reaches validationSchema through toTypedSchema(), which is also what supplies the types. useFieldArray drives repeatable groups and iterates on field.key. Everything here is v4; v5 removes the adapter, so check reference.md before targeting it.

Detailed Resources:

  • examples/core.md — defineField, inline rules, a reusable input built on useField, form meta, eager validation
  • examples/validation.md — schema through toTypedSchema, conditional schemas, all errors per field
  • examples/arrays.md — useFieldArray, nested arrays, reordering
  • reference.md — return-value tables, composition helpers, worked anti-patterns, v5 migration

Which path applies

  • The form is built in the component around native inputs — useForm plus defineField, and v-model on the returned model. Follow examples/core.md.
  • A reusable input component — useField inside the component, taking its name as () => props.name. Follow examples/core.md Pattern 3.
  • The renderless <Form> and <Field> components — an alternative to the Composition API 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>

Before writing VeeValidate code

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:

  • Vue form state, validation timing and submission
  • Reusable input components that bind themselves to a named field
  • Repeatable field groups that add, remove and reorder
  • Surfacing server-side validation failures against individual fields
  • Multi-step forms where fields unmount between steps

Handled elsewhere:

  • Authoring the validation schema — toTypedSchema adapts whatever schema it is given, and how that schema states its rules is settled by whatever owns it.
  • Choosing a schema library — an adapter package exists for each, and the choice sits outside this skill.
  • Markup and styling of the inputs — every example uses plain elements, so the classes are yours.
  • Where the initial values came from — 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 or useField

defineFielduseField
Returns[model, attrs] for v-model and v-bindvalue, errorMessage, meta, handlers
Form contextRequired — it comes off a useForm resultOptional; falls back to standalone validation
Best forThe form's own template, native inputsA 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>

Core patterns

Pattern 1: useForm with defineField

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


Pattern 2: useField in a reusable input

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


Pattern 3: Schema through toTypedSchema

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


Pattern 4: useFieldArray

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


Pattern 5: Server-side errors

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.


Pattern 6: Form meta

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>

Red flags

Breaks at runtime:

  • A schema handed to validationSchema without toTypedSchema() — it is accepted and then never validates, so the form submits whatever it holds.
  • props.name passed to useField instead of a getter — the field binds to the name captured at setup and ignores every later change.
  • initialValues missing a field array — the composable iterates undefined and throws.
  • An array index as the useFieldArray iteration key — Vue matches the wrong entries, so removing a middle row shifts every value below it up one.
  • resetForm(data) in place of resetForm({ values: data }) — the argument shape is wrong, and the reset takes no effect rather than reporting.

Surprising behaviour:

  • errors holds the first error per field; errorBag holds all of them as arrays. A password rule set 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 it alone starts disabled.
  • keepValuesOnUnmount defaults to false, so a field that unmounts loses its value. Set it true for anything that hides fields between steps.
  • Nested fields address by dot notation (defineField("user.profile.name")) and array items by brackets (errors["items[0].name"]) — the two are not interchangeable.
  • A computed schema must be passed as the computed itself, not .value — unwrapped, the form binds to one snapshot and stops tracking it.
  • validateOnValueUpdate left on validates every keystroke, including the first.
  • Errors shown without checking 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>

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Hugging Face Inference SDK patterns for TypeScript/Node.js — InferenceClient setup, chat completion, text generation, streaming, embeddings, image generation, audio transcription, translation, summarization, and Inference Endpoints

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

LiteLLM proxy server setup, TypeScript client patterns via OpenAI SDK, model routing, fallbacks, load balancing, spend tracking, virtual keys, and production deployment

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Serverless GPU compute platform for AI model deployment — web endpoints, GPU functions, model serving, and TypeScript client patterns

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Local LLM inference with the Ollama JavaScript client -- chat, streaming, tool calling, vision, embeddings, structured output, model management, and OpenAI-compatible endpoint

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Replicate SDK patterns for TypeScript/Node.js -- client setup, predictions, streaming, webhooks, file handling, model versioning, deployments, and training

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Together AI SDK patterns for TypeScript — client setup, chat completions, streaming, structured output, function calling, embeddings, image generation, fine-tuning, and OpenAI-compatible endpoints

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

agents-inc のスキルをすべて見る

このスキルの問題を報告する