Choosing Between VeeValidate’s Component and Composition APIs in Vue 3
Decide whether to use VeeValidate’s component‑based API or the Composition API for Vue 3 forms. See a concise trade‑off table, concrete code examples, and practical verification steps.
10 Oct 2025, 04:30 UTC

Decision and constraints
When building a form with Vue 3 and VeeValidate 4.x you must decide whether to use the declarative <Form>/<Field> components or the programmatic Composition API (useForm, useField, defineField). Both APIs share the same validation engine, so the choice is about template style, control needs, and integration with UI libraries or TypeScript.
API comparison
| Aspect | Component API | Composition API |
|---|---|---|
| Setup style | Template‑driven, minimal script | Script‑first, returns refs for values, errors, meta |
| Best for | Static or lightly dynamic forms, quick prototypes | Dynamic forms, multi‑step wizards, UI libraries that expect explicit :value bindings, full TypeScript inference |
| Access to form state | Via template refs or useForm if mixed | Directly as returned refs (values, errors, handleSubmit) |
| Schema validation | Pass validationSchema prop to <Form> | Pass validationSchema to useForm |
| Learning curve | Low for designers/template‑focused teams | Higher, requires familiarity with Vue Composition API |
Trade‑offs
- Component API reduces boilerplate: each input is wrapped in
<Field>and VeeValidate handles thev‑modelbinding internally. This speeds up development for forms that rarely change structure. - Composition API gives you programmatic control: you can reset fields imperatively, watch specific values, or build multi‑step wizards where each step renders a different subset of fields. It also works naturally with UI component libraries that require explicit
:valueand@update:valueprops. - Both APIs support schema validation via official adapters (
@veevalidate/yupor@veevalidate/zod). Using a schema centralises rules and enables TypeScript inference when using Zod. - Mixing approaches is possible but adds complexity and is rarely needed.
Concrete implementation – Component API
Below is a minimal login form that uses a Zod schema. The template stays declarative; the script only registers the schema.
<Form @submit.prevent="onSubmit" :validation-schema="loginSchema">
<Field name="email">
<template #default="{ field, meta }">
<input v-bind="field" type="email" placeholder="Email" />
<span class="error" v-if="meta.touched && meta.invalid">{{ meta.errors[0] }}
</template>
</Field>
<Field name="password">
<template #default="{ field, meta }">
<input v-bind="field" type="password" placeholder="Password" />
<span class="error" v-if="meta.touched && meta.invalid">{{ meta.errors[0] }}
</template>
</Field>
<button type="submit" :disabled="isSubmitting">Sign in</button>
</Form>
When the user blurs an input, VeeValidate runs the corresponding Zod rule and shows the error via the meta.errors array. Submitting with invalid data prevents onSubmit from executing.
Concrete implementation – Composition API
The same form built with the Composition API gives you direct access to form state. This is useful when you need to enable/disable the submit button based on isSubmitting or reset the form after a successful response.
<form @submit.prevent="handleSubmit(onValid)">
<div>
<label for="email">Email:</label>
<input id="email" v-model="email" type="email" />
<span class="error" v-if="emailError.value">{{ emailError.value }}</span>
</div>
<div>
<label for="password">Password:</label>
<input id="password" v-model="password" type="password" />
<span class="error" v-if="passwordError.value">{{ passwordError.value }}</span>
</div>
<button type="submit" :disabled="isSubmitting.value">Sign in</button>
</form>
Here email and password are refs that update reactively. The handleSubmit function returned by useForm only calls onValid when validation passes, mirroring the behavior of the component API.
When to choose which
- Component API if:
- Your form is static or only lightly dynamic.
- You prefer keeping validation logic in the template.
- Team members are more comfortable with HTML‑centric code.
- Composition API if:
- You need to manipulate form values imperatively (e.g., reset a wizard step).
- You are integrating with a UI library that requires explicit
:valueand@update:valueprops. - You want full TypeScript inference from a Zod schema without extra wrappers.
- You are building a multi‑step form where each step may have different validation groups.
Limitations and verification
Both APIs rely on VeeValidate’s internal event listening for native input and change events. Custom UI components that do not emit these events need manual v-model wiring or the use of useField to connect values and errors. Always verify that the major version of veevalidate matches the adapter packages (@veevalidate/zod or @veevalidate/yup) to avoid type mismatches.
A quick way to confirm your choice works:
- Create a Vue 3 + Vite project and install
vee-validate@^4and the desired adapter. - Implement the form once with the Component API and once with the Composition API using the same Zod schema.
- Attempt to submit with invalid data; verify that the submit handler is not called and that field‑level errors appear.
- Submit with valid data and check that the handler receives the correctly typed values.
If errors appear, inspect the browser console for warnings about missing rules or version mismatches, and consult the VeeValidate migration guide for your exact version.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.