Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesSome links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
In Vue 3, validate a form by keeping its values in reactive state, defining rules that return field errors, showing those errors at the right interaction point, and running the complete check again before submission. For small forms, plain Vue is enough; for nested data, schemas, asynchronous rules, or reusable components, Vuelidate and vee-validate provide the structure.
Use Vue 3 and a real HTML form
Vue 3 is the current baseline; Vue 2 support ended on December 31, 2023. Start with semantic HTML rather than replacing browser controls with click handlers. Use <form>, a <label> for every control, an <input>, <textarea> or <select> as appropriate, and a submit <button>. Match each label’s for attribute to the control’s id.
Vue’s v-model synchronizes these controls with JavaScript state. Text fields use the value property and input event; checkboxes and radios use checked and change; selects use value and change. This synchronization is not validation by itself: required fields, formats, cross-field rules, and server constraints still need explicit checks.
What the modifiers do
v-model.lazyupdates onchangeinstead of everyinput.v-model.numberattempts to cast the value to a number.v-model.trimremoves surrounding whitespace.
Choose modifiers for data behavior, not as a substitute for a rule. A trimmed empty string can still be invalid, and numeric casting does not prove that a value is in range.
#1 Best Overall
A complete hand-written Vue 3 validator
This example validates a name, email, password, and confirmation. Errors appear after a field is blurred or after the first submit attempt. The same rule functions run in both the display path and the final submit path, so pressing Enter cannot bypass validation.
<script setup>
import { computed, reactive, ref } from 'vue'
const values = reactive({
name: '',
email: '',
password: '',
confirmPassword: ''
})
const touched = reactive({
name: false,
email: false,
password: false,
confirmPassword: false
})
const submitted = ref(false)
const serverError = ref('')
const rules = {
name(value) {
if (!value.trim()) return 'Enter your name.'
if (value.trim().length < 2) return 'Use at least 2 characters.'
return ''
},
email(value) {
if (!value.trim()) return 'Enter an email address.'
return /^[^s@]+@[^s@]+.[^s@]+$/.test(value)
? ''
: 'Enter a valid email address.'
},
password(value) {
if (value.length < 8) return 'Use at least 8 characters.'
return ''
},
confirmPassword(value) {
if (value !== values.password) return 'Passwords do not match.'
return ''
}
}
const errors = computed(() => ({
name: rules.name(values.name),
email: rules.email(values.email),
password: rules.password(values.password),
confirmPassword: rules.confirmPassword(values.confirmPassword)
}))
const isInvalid = computed(() => Object.values(errors.value).some(Boolean))
function showError(field) {
return (touched[field] || submitted.value) ? errors.value[field] : ''
}
function touch(field) {
touched[field] = true
}
async function submit() {
submitted.value = true
serverError.value = ''
Object.keys(touched).forEach(field => { touched[field] = true })
if (isInvalid.value) return
try {
// Replace with your authenticated API request.
await saveAccount({ ...values })
} catch (error) {
serverError.value = 'We could not save the form. Try again.'
}
}
async function saveAccount(payload) {
// Example placeholder; do not treat client validation as authorization.
return payload
}
</script>
<template>
<form novalidate @submit.prevent='submit'>
<div>
<label for='name'>Name</label>
<input id='name' v-model.trim='values.name' name='name'
autocomplete='name' :aria-invalid='!!showError("name")'
aria-describedby='name-error' @blur='touch("name")' />
<p v-if='showError("name")' id='name-error' role='alert'>{{ showError('name') }}</p>
</div>
<div>
<label for='email'>Email</label>
<input id='email' v-model.trim='values.email' name='email' type='email'
autocomplete='email' :aria-invalid='!!showError("email")'
aria-describedby='email-error' @blur='touch("email")' />
<p v-if='showError("email")' id='email-error' role='alert'>{{ showError('email') }}</p>
</div>
<div>
<label for='password'>Password</label>
<input id='password' v-model='values.password' name='password' type='password'
autocomplete='new-password' :aria-invalid='!!showError("password")'
aria-describedby='password-error' @blur='touch("password")' />
<p v-if='showError("password")' id='password-error' role='alert'>{{ showError('password') }}</p>
</div>
<div>
<label for='confirm-password'>Confirm password</label>
<input id='confirm-password' v-model='values.confirmPassword' type='password'
autocomplete='new-password' :aria-invalid='!!showError("confirmPassword")'
aria-describedby='confirm-password-error' @blur='touch("confirmPassword")' />
<p v-if='showError("confirmPassword")' id='confirm-password-error' role='alert'>{{ showError('confirmPassword') }}</p>
</div>
<p v-if='serverError' role='alert'>{{ serverError }}</p>
<button type='submit'>Create account</button>
</form>
</template>
novalidate is optional. It is useful when you want one consistent error presentation, but remove it if you also want the browser’s built-in constraint UI. The API must validate the payload again, including authorization, uniqueness, and any security-sensitive rule.
Choose when errors become visible
On input
Validate on every change when feedback is immediate and inexpensive, such as a character counter or password length. Avoid showing an error while a user is still typing an email address or a multi-part value.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →On blur (touched)
Mark a field touched on blur and show its message afterward. This is a good default for most text fields because the user gets feedback after leaving the control without being interrupted on every keystroke.
On submit
On the first submit attempt, mark every field touched, render all errors, and move focus to the first invalid control. Keep a submit counter if your analytics or UI needs to distinguish repeated attempts.
Async rules
Debounce checks such as username availability, show a pending state, and ignore an older response if a newer value has already been entered. A failed network request should be presented as an availability or retry message, not silently treated as a valid value.
Vuelidate or vee-validate?
Both are Vue 3 choices, but they organize validation differently.
| Concern | Hand-written rules | Vuelidate Next | vee-validate v4 |
|---|---|---|---|
| Rule style | Functions you own | Rules attached to a reactive model tree | Field functions or form-level schemas |
| Best shape | Small, mostly flat forms | Nested models and repeatable collections | Component-driven forms and schema boundaries |
| Timing and state | You implement touched, pending, and submit state | Model validation state from useVuelidate() |
Explicit valid, dirty, touched, pending, errors, and submit state |
| Schema and TypeScript | Manual types and adapters | Custom predicates and composed rules | Yup, Zod, and Valibot integrations with typed adapters |
| Submission and reset | Your submit and reset functions | Your submit flow around the model | Built-in controlled submission, reset, validation, and invalid-submit hooks |
| Custom components | Manual event/value contracts | Model binding you define | useField and component APIs are designed for integration |
Use Vuelidate for model-centric validation
Vuelidate is lightweight and decoupled. Its Vue 3 package, Vuelidate Next, is a natural fit when rules mirror a domain object, especially with nested models, collections, contextual validators, or custom predicates.
<script setup>
import { reactive } from 'vue'
import useVuelidate from '@vuelidate/core'
import { required, email, minLength, sameAs } from '@vuelidate/validators'
const state = reactive({ email: '', password: '', confirm: '' })
const rules = {
email: { required, email },
password: { required, minLength: minLength(8) },
confirm: { sameAs: sameAs(() => state.password) }
}
const v$ = useVuelidate(rules, state)
async function submit() {
const valid = await v$.value.$validate()
if (!valid) return
// Send state to the server.
}
</script>
<template>
<form @submit.prevent='submit'>
<input v-model='state.email' @blur='v$.email.$touch()' />
<p v-if='v$.email.$error'>Enter a valid email.</p>
<button>Save</button>
</form>
</template>
Use the validation object’s dirty and error state to decide when to display messages; do not expose every rule failure before the user interacts with the field.
Use vee-validate for field and schema workflows
vee-validate v4 uses Vue 3 Composition API primitives such as useForm and useField. Its Form, Field, and ErrorMessage components provide a template-oriented alternative. It supports synchronous and asynchronous validation at field or form level, including Yup, Zod, and Valibot schemas.
<script setup>
import { Form, Field, ErrorMessage } from 'vee-validate'
import * as yup from 'yup'
const schema = yup.object({
email: yup.string().required('Email is required').email('Enter a valid email'),
password: yup.string().required('Password is required').min(8, 'Use at least 8 characters')
})
function onSubmit(values, actions) {
// Values passed here already passed schema validation.
return fetch('/api/account', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values)
}).catch(() => {
actions.setFieldError('email', 'The server could not save this address.')
})
}
</script>
<template>
<Form :validation-schema='schema' @submit='onSubmit'>
<label for='v-email'>Email</label>
<Field id='v-email' name='email' type='email' />
<ErrorMessage name='email' as='p' role='alert' />
<label for='v-password'>Password</label>
<Field id='v-password' name='password' type='password' />
<ErrorMessage name='password' as='p' role='alert' />
<button type='submit'>Create account</button>
</Form>
</template>
vee-validate validates before the submit handler runs. Invalid submissions are stopped by default. Use handleSubmit for JavaScript or AJAX submission, submitForm when you want native submission preserved while blocking invalid data, and validate when you need to run checks without submitting. Its submission API also exposes invalid-submit events, pending and touched transitions, submit counts, and reset methods.
Typed schemas and reactive rules
Keep a schema at the form boundary so the same contract is visible to the UI and the request layer. vee-validate’s typed adapters include @vee-validate/yup and @vee-validate/zod; toTypedSchema can infer input and output types. Zod can be used as a drop-in alternative through @vee-validate/zod.
Do not recreate a large schema on every render. Define it once when rules are static. If a rule depends on reactive state, either use the library’s supported reactive schema mechanism or keep the dependent predicate small and explicit. The server remains authoritative when its constraints differ from the client schema.
Submission, server errors, and reset behavior
- Initialize values, including empty values for optional fields.
- Run field checks on blur or the interaction policy you selected.
- On submit, mark all fields touched and validate the complete model.
- Focus the first invalid control and ensure its message is announced.
- Send only validated client data, then process the server response.
- Map field-specific server errors back to the corresponding control; keep a separate form-level message for errors that do not belong to one field.
- Reset values, touched state, submit count, and server errors together after a successful save or an explicit reset action.
Never use a client result to authorize an operation, enforce uniqueness, or protect an endpoint. A user can bypass JavaScript entirely.
Accessible error messaging
- Keep the visible label associated with the control through matching
for/idvalues. - Give each error a stable
idand connect it witharia-describedby. - Set
aria-invalid='true'only when the field currently has an error. - Use
role='alert'for concise, dynamic messages; avoid placing an entire changing form inside one live region. - Do not rely on red color alone. Preserve text and a clear focus outline.
- When submit reveals several errors, move focus to the first invalid control and leave the rest in document order.
- Use appropriate input types and autocomplete tokens so browsers and assistive technology understand the control.
Troubleshooting common failures
The form submits despite visible errors
Make the submit handler the form’s @submit.prevent handler and return immediately when the complete error collection is non-empty. With vee-validate, submit through handleSubmit or the Form component rather than a separate click handler.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →An error appears before the user types
Your template is rendering the rule result directly. Gate it behind a touched flag or submit state, or use the library’s dirty/touched metadata.
Cross-field confirmation is stale
Compute the confirmation rule from the current password value rather than capturing the initial value. In a library, use a reactive predicate or schema reference.
Async checks overwrite newer results
Debounce requests and associate each response with the value that initiated it. Discard responses for older values, and expose a pending state so users know why submission is temporarily unavailable.
Custom inputs never become touched
Forward the component’s value update and blur event to the validation layer. A wrapper that emits only a custom event must translate it into the event or model contract expected by useField, Vuelidate, or your own touch function.
Recommended Free Tools
Server errors disappear on the next keystroke
Store server errors separately from client rule errors. Clear a field’s server message only when that field changes, and retain form-level failures until the next submission attempt.
Best Value
Browser and custom messages conflict
Either use native constraint validation consistently or add novalidate and render your own messages. Mixing both without a policy can produce duplicate announcements.
Performance, reliability, and cost decisions
- Keep inexpensive synchronous rules local and debounce network-backed rules.
- Validate only the fields or collection rows that changed when a form is large.
- Do not watch an entire reactive object deeply if a field-level watcher is sufficient.
- Lazy-load a validation library for a route that contains a rarely used, complex form.
- Keep error messages deterministic and preserve the user’s entered values after a failed request.
- Test keyboard submission, blur order, screen-reader announcements, async cancellation, server field errors, and reset behavior.
For security and reliability, duplicate all meaningful constraints on the server and handle timeouts, retries, and malformed responses as explicit states. Client validation improves feedback; it does not establish trust.
Or skip the browser setup
If your development task is to capture a page for a form review, regression artifact, or documentation image, ScreenshotNeo returns a screenshot or PDF from one request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots.
Free tools Windows power users keep installed
One-click scans. No signup required.
See the ScreenshotNeo API documentation for all capture options, including full-page lazy-image loading, CSS-selector element capture, device presets, custom CSS and JavaScript, waits, request blocking, cookies, headers, geolocation, PDF settings, caching, signed links, asynchronous webhooks, bulk capture, and usage reporting.
cURL
curl -G 'https://api.screenshotneo.com/v1/shot' -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://stripe.com'}, timeout=90)
r.raise_for_status()
open('shot.webp', 'wb').write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Use an API key kept on your server, not in browser JavaScript. To try it, sign up for ScreenshotNeo’s free plan with 1,000 screenshots each month and no card required.
Frequently Asked Questions
How should I test a Vue validation flow?
Test rule functions separately, then add component tests for blur, submit, reset, and server-error rendering. Include keyboard-only and screen-reader checks, plus an end-to-end test that submits invalid data and confirms no request is sent.
How can I localize validation messages?
Return message keys or structured error codes from rules, then resolve them through your i18n layer at render time. Keep field names and interpolation values separate from translated text so changing locale does not require rebuilding the validation model.
What is the safest way to validate a dynamic list of fields?
Give each row a stable identity, validate additions and removals against the current collection, and send the server the complete normalized list. Model-centric trees such as Vuelidate are useful when each row has nested rules.
Can I use native browser validation with vee-validate or Vuelidate?
Yes, but choose one visible error policy. Keep native constraints when their UI meets your accessibility needs, or use `novalidate` and let the Vue layer own messages, focus, and announcements.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

