@formkit/vue and vuelidate are both libraries designed to handle form validation in Vue.js applications, but they take very different approaches. @formkit/vue is part of the FormKit ecosystem, which provides a complete form-building solution that includes UI components, validation, accessibility, and state management out of the box. vuelidate, on the other hand, is a lightweight, model-based validation library that focuses solely on validating data against defined rules without providing any UI components.
Both @formkit/vue and vuelidate help Vue developers validate user input, but they solve different problems. @formkit/vue is a full-featured form authoring framework, while vuelidate is a focused validation utility. Let’s compare how they work in practice.
@formkit/vue provides everything needed to build forms — from input components to validation logic to accessibility features. You define your form schema declaratively, and FormKit handles rendering, state management, and validation.
<!-- @formkit/vue: Complete form with built-in UI -->
<script setup>
import { FormKit } from '@formkit/vue'
const formData = {}
function handleSubmit(data) {
console.log(data)
}
</script>
<template>
<FormKit
type="form"
:actions="false"
@submit="handleSubmit"
>
<FormKit
name="email"
type="email"
label="Email address"
validation="required|email"
/>
<FormKit
name="password"
type="password"
label="Password"
validation="required|length:8"
/>
</FormKit>
</template>
vuelidate only validates data — it doesn’t render anything. You pair it with your own form markup and manage DOM interactions yourself.
<!-- vuelidate: Validation logic only -->
<script setup>
import useVuelidate from '@vuelidate/core'
import { required, email, minLength } from '@vuelidate/validators'
import { reactive } from 'vue'
const state = reactive({
email: '',
password: ''
})
const rules = {
email: { required, email },
password: { required, minLength: minLength(8) }
}
const v$ = useVuelidate(rules, state)
function handleSubmit() {
v$.value.$validate()
if (!v$.value.$error) {
console.log(state)
}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="state.email" placeholder="Email" />
<div v-if="v$.email.$error">{{ v$.email.$errors[0].$message }}</div>
<input v-model="state.password" type="password" placeholder="Password" />
<div v-if="v$.password.$error">{{ v$.password.$errors[0].$message }}</div>
<button type="submit">Submit</button>
</form>
</template>
@formkit/vue uses its own component system. While you can create custom inputs using FormKit’s plugin architecture, you’re generally working within FormKit’s rendering pipeline.
// @formkit/vue: Creating a custom input
import { createInput } from '@formkit/vue'
const CustomRating = createInput({
// component definition
})
// Then register globally or locally
vuelidate works with any input component because it only cares about the data model. This makes it easy to plug into existing design systems.
<!-- vuelidate with Vuetify -->
<template>
<v-text-field
v-model="state.email"
:error-messages="v$.email.$errors.map(e => e.$message)"
label="Email"
/>
</template>
@formkit/vue uses string-based validation rules that are applied directly on FormKit nodes.
<FormKit
name="age"
validation="required|number|between:18,99"
/>
You can also write custom validation functions:
const customValidator = (node) => {
return node.value.length > 5 ? true : 'Must be longer than 5 characters'
}
vuelidate defines validation rules as JavaScript objects using validator functions from @vuelidate/validators or custom functions.
const rules = {
username: {
required,
unique: async (value) => {
const exists = await checkUsernameExists(value)
return !exists
}
}
}
For cross-field validation (e.g., “password and confirm password must match”), both libraries support it but with different patterns.
@formkit/vue uses the $get helper to reference other fields in validation:
<FormKit
name="confirmPassword"
validation="required|confirm:@password"
/>
Or with a custom function:
const confirmPassword = (node) => {
const password = node.parent?.$get('password')?.value
return node.value === password || 'Passwords do not match'
}
vuelidate handles cross-field validation by defining validators at the root level or using sibling references:
const rules = {
password: { required, minLength: minLength(8) },
confirmPassword: {
required,
sameAsPassword: sameAs(state.password)
}
}
@formkit/vue automatically manages ARIA attributes, focus management, error announcements for screen readers, and live regions. Input states (like loading indicators during async validation) are built in.
vuelidate leaves accessibility entirely up to you. You must manually wire up ARIA attributes, manage focus traps, and ensure error messages are announced.
Both support asynchronous validation, but the implementation differs.
@formkit/vue allows async validators that return promises:
const checkEmail = async (node) => {
const available = await api.checkEmail(node.value)
return available || 'Email already taken'
}
vuelidate supports async validators through the same object syntax:
const rules = {
email: {
async isUnique(value) {
if (!value) return true
const exists = await api.emailExists(value)
return !exists
}
}
}
@formkit/vue has a larger footprint because it includes UI components, theming, and state management. However, it reduces the amount of boilerplate you write for common form patterns.
vuelidate is much smaller since it only handles validation logic. But you’ll write more code to connect validation state to your UI and handle edge cases like debouncing or submission flow.
@formkit/vue when:vuelidate when:Think of @formkit/vue as a complete workshop for building forms — it gives you tools, materials, and blueprints. vuelidate is like a high-precision measuring tape — it does one job extremely well, but you bring everything else. Choose based on whether you need a full form solution or just validation logic.
Choose vuelidate if you already have your form UI built with custom components or another library (like Vuetify or Element Plus) and only need a flexible, composable validation layer. It's well-suited for projects where you want fine-grained control over validation logic without being tied to a specific component system.
Choose @formkit/vue if you want an all-in-one form solution that handles UI rendering, validation, accessibility, and complex form state (like conditional fields or multi-step forms) with minimal configuration. It's ideal when you prioritize developer velocity and consistent UX across forms, and don't mind adopting a more opinionated framework for form building.
Simple, lightweight model-based validation for Vue.js
Vue 3 support is almost here with the Vuelidate 2 rewrite. Check out the next branch to see the latest progress.
npm install vuelidate --save
You can import the library and use as a Vue plugin to enable the functionality globally on all components containing validation configuration.
import Vue from 'vue'
import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)
Alternatively it is possible to import a mixin directly to components in which it will be used.
import { validationMixin } from 'vuelidate'
var Component = Vue.extend({
mixins: [validationMixin],
validations: { ... }
})
The browser-ready bundle is also provided in the package.
<script src="vuelidate/dist/vuelidate.min.js"></script>
<!-- The builtin validators is added by adding the following line. -->
<script src="vuelidate/dist/validators.min.js"></script>
Vue.use(window.vuelidate.default)
For each value you want to validate, you have to create a key inside validations options. You can specify when input becomes dirty by using appropriate event on your input box.
import { required, minLength, between } from 'vuelidate/lib/validators'
export default {
data () {
return {
name: '',
age: 0
}
},
validations: {
name: {
required,
minLength: minLength(4)
},
age: {
between: between(20, 30)
}
}
}
This will result in a validation object:
$v: {
name: {
"required": false,
"minLength": false,
"$invalid": true,
"$dirty": false,
"$error": false,
"$pending": false
},
age: {
"between": false
"$invalid": true,
"$dirty": false,
"$error": false,
"$pending": false
}
}
Checkout the docs for more examples: https://vuelidate.js.org/
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# create UMD bundle.
npm run build
# Create docs inside /gh-pages ready to be published
npm run docs
# run unit tests
npm run unit
# run all tests
npm test
For detailed explanation on how things work, checkout the guide and docs for vue-loader.
|
Damian Dulisz |
Natalia Tepluhina |
Dobromir Hristov |
Marina Mosti |
Here we honor past contributors who have been a major part on this project.