vuelidate vs @formkit/vue
Form Validation and Management in Vue Applications
vuelidate@formkit/vueSimilar Packages:
Form Validation and Management in Vue Applications

@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.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
vuelidate150,6586,904104 kB2144 years agoMIT
@formkit/vue86,3334,6351.89 MB1482 months agoMIT

Form Validation Approaches: @formkit/vue vs vuelidate

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.

🧱 Architecture: Full Framework vs Validation-Only Utility

@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>

🔌 Integration with Custom Components

@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>

📝 Validation Definition Style

@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
    }
  }
}

⚡ Handling Complex Validation Scenarios

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)
  }
}

🌐 Accessibility and UX Features

@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.

🔄 Async Validation Support

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
    }
  }
}

📦 Bundle Impact and Learning Curve

@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.

🛠️ When Each Shines

Use @formkit/vue when:

  • You’re starting a new project and want consistent, accessible forms fast
  • You need built-in support for complex form patterns (conditional fields, multi-step wizards)
  • You prefer convention over configuration for form building
  • Your team includes designers who can leverage FormKit’s theme system

Use vuelidate when:

  • You already have a mature UI component library
  • You only need validation logic without UI opinions
  • You require maximum flexibility in how validation errors are displayed
  • You’re working on a legacy codebase where replacing form markup isn’t feasible

💡 Final Thought

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.

How to Choose: vuelidate vs @formkit/vue
  • vuelidate:

    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.

  • @formkit/vue:

    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.

README for vuelidate

vuelidate

codecov gzip size

Simple, lightweight model-based validation for Vue.js

Sponsors

Gold

Vuejs Amsterdam

Vue - The Road To Enterprise

Silver

Storyblok

Bronze

Vue Mastery logo Vue Mastery logo

Features & characteristics:

  • Model based
  • Decoupled from templates
  • Dependency free, minimalistic library
  • Support for collection validations
  • Support for nested models
  • Contextified validators
  • Easy to use with custom validators (e.g. Moment.js)
  • Support for function composition
  • Validates different data sources: Vuex getters, computed values, etc.

Demo & docs

https://vuelidate.js.org/

Vue 3 support

Vue 3 support is almost here with the Vuelidate 2 rewrite. Check out the next branch to see the latest progress.

Installation

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)

Basic usage

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/

Contributing

# 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.

Contributors

Current

Damian Dulisz
Damian Dulisz
Natalia Tepluhina
Natalia Tepluhina
Natalia Tepluhina
Dobromir Hristov
Marina Mosti
Marina Mosti

Emeriti

Here we honor past contributors who have been a major part on this project.

License

MIT