Validation Provider

The ValidationProvider component is a regular component that wraps your inputs and provides validation state using scoped slots.

TIP

The slots syntax has been changed in Vue 2.6, the following examples use the new v-slot syntax instead of the deprecated slot-scope, but it is still supported and you can use it. However v-slot have different semantics, consult the Vue docs for more information.

Using the ValidationProvider offers isolated scope for each field validation state, and does not inject/mutate anything outside its slot. You can import it and use whenever you need it. Using the validation context will allow you to apply classes, flags and pass state to your template.

Here is a quick example:

<template>
  <ValidationProvider rules="required" v-slot="{ errors }">
    <input v-model="value" type="text">
    <span id="error">{{ errors[0] }}</span>
  </ValidationProvider>
</template>

<script>
import { ValidationProvider } from 'vee-validate';

export default {
  components: {
    ValidationProvider
  }
};
</script>

It also works for custom components and solves the issue of authoring self validated components, which is hard to achieve normally because of the directive limitations.

TIP

The fields being validated must have a v-model so the component can correctly identify the element/component being validated unless the field accepts a file.

Rendering

By default, ValidationProvider renders a span, Consider the following example:

  <ValidationProvider rules="required" v-slot="{ errors }">
    <input v-model="value" type="text">
    <span>{{ errors[0] }}</span>
  </ValidationProvider>

  <!-- HTML Output -->
  <span>
    <input type="text">
    <span>ERROR_MSG_PLACEHOLDER</span>
  </span>

The default tag can be changed using the provider's tag prop.

  <!-- Multiple Child nodes using templates -->
  <ValidationProvider rules="required" tag="div">
    <template v-slot="{ errors }">
      <input v-model="value" type="text">
      <span>{{ errors[0] }}</span>
    </template>
  </ValidationProvider>

  <!-- Multiple Child nodes directly -->
  <ValidationProvider rules="required" v-slot="{ errors }" tag="div">
    <input v-model="value" type="text">
    <span>{{ errors[0] }}</span>
  </ValidationProvider>

  <!-- Both have the same HTML Output -->
  <div>
    <input type="text">
    <span>ERROR_MSG_PLACEHOLDER</span>
  </div>

Renderless

Sometimes it is unsuitable for a Provider component in principle to render anything extra, because of limitations in the Vue rendering engine we cannot have multiple root nodes which limits the design choice to move away from renderless at the moment, in Vue 3.x it this may change with fragments.

In 2.2.10 a slim prop can be used to force the component to be renderless, by default it is set to false.

<ValidationProvider rules="required" v-slot="{ errors }" slim>
  <div>
    <input v-model="value" type="text">
    <span>{{ errors[0] }}</span>
  </div>
</ValidationProvider>

<!-- HTML Output, NO OUTER SPANS -->
<div>
  <input type="text">
  <span>ERROR_MSG_PLACEHOLDER</span>
</div>

Note that only the first child will be rendered when slim is used, any other nodes will be dropped as you cannot have multiple root nodes in a renderless component. Be mindful of that when using the slim prop.

<ValidationProvider rules="required" v-slot="{ errors }" slim>
  <input v-model="value" type="text">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

<!-- Only input is rendered, the span is dropped -->
<input type="text">

Scoped Slot Data

The object passed down to the slot scope is called the validation context. It has the following properties:

Name Type Description
errors string[] The list of error messages.
failedRules [x: string]: string A map object of failed rules with (rule, message) as a (key, value)
valid boolean | null The current validation state.
flags { [x: string]: boolean } The flags map object state.
aria { [x: string]: string } Map object of aria attributes for accessibility.
classes { [x: string]: boolean } Map object of the classes configured based on the validation state.
validate (e: any) => Promise A function that is used as an event handler to trigger validation. Useful for fields that do not use v-model.
reset () => void A function that resets the validation state on the provider.

Since slot scopes can take advantage of ES6 destructing, you can opt-in for any of those properties and pass down to your slot template as you see fit. The example above only needed the errors array.

Examples

The previous quick sample validates simple HTML inputs, lets take this up a notch and validate popular 3rd party components like Vuetify's TextInput.

Basic Example

This passes error messages down to Vuetify's text field component.

<ValidationProvider rules="required" v-slot="{ errors }">
  <VTextField v-model="value" :error-messages="errors" />
</ValidationProvider>

Manual Validation

Triggering validation on any of the providers is simple, but it is opt-in. Meaning you need to explicitly call the validation on the provider instance. Using refs and the public method validate makes it straight forward.

<template>
  <div>
    <ValidationProvider rules="required" ref="myinput" v-slot="{ errors }">
      <VTextField v-model="value" :error-messages="errors" />
    </ValidationProvider>

    <v-btn @click="validateField('myinput')" >Submit</v-btn>
  </div>
</template>

<script>
export default {
  // ...
  methods: {
    validateField (field) {
      const provider = this.$refs[field];

      // Validate the field
      return provider.validate();
    }
  },
  // ..
};
</script>

If you only plan to trigger manual validation using the UI, you can use the validate handler on the v-slot data to trigger validation without having to use refs.

<ValidationProvider rules="required" v-slot="{ validate, errors }">
  <div>
    <input type="text" @input="validate">
    <p id="error">{{ errors[0] }}</p>
  </div>
</ValidationProvider>

Note that the validate method on the validation handler, you can use the $event in the Vue template to reference the event arg that is emitted with the event if your handlers are more complex.

<ValidationProvider rules="required" v-slot="{ validate, errors }">
  <div>
    <input type="text" @input="syncVuex($event.target.value) || validate($event)">
    <p id="error">{{ errors[0] }}</p>
  </div>
</ValidationProvider>

Validation Result

The validate method returns a validation result wrapped in a promise, so normally you either would use then or await for the validation to finish. The validation result contains useful information about the validation attempt:

interface ValidationResult {
  errors: string[]; // Array of error messages
  valid: boolean; // boolean flag
  failedRules: { [x: string]: string } // A map of rule names and their error message.
}

TIP

Using the same approach you can reset validation state for the provider using the public method reset() and the slot scope method of the same name.

File Validation

While v-model is generally required when using the ValidationProvider component, some inputs like file do not benefit from having v-model. Instead, you can use the manual validate method to avoid having to use v-model in this instance.

<ValidationProvider rules="required" v-slot="{ validate, errors }">
  <div>
    <input type="file" @change="handleFileChange($event) || validate($event)">
    <p id="error">{{ errors[0] }}</p>
  </div>
</ValidationProvider>

Input Groups (Checkbox/Radio)

Like radio inputs and (sometimes) checkboxes, some inputs behave as a single input entity. You can wrap a whole group of inputs given that they have the same v-model in a single ValidationProvider component. You can group as many inputs as you want inside the ValidationProvider component.

<ValidationProvider rules="required" v-slot="{ errors }">
  <div>
    <input type="radio" v-model="drink" value="">
    <input type="radio" v-model="drink" value="coffee">
    <input type="radio" v-model="drink" value="coke">
  </div>
</ValidationProvider>

Cross-Field Validation

When using the directive, the confirmed rule targets the other field that has a corresponding ref. Using the ValidationProvider is slightly different; it looks for a ValidationProvider component that has a matching vid prop. The vid can be either a number or a string.

<ValidationProvider rules="required|confirmed:confirm" v-slot="{ errors }">
  <VTextField v-model="password" type="password" :error-messages="errors" />
</ValidationProvider>

<ValidationProvider vid="confirm" rules="required" v-slot="{ errors }">
  <VTextField v-model="passwordConfirm" type="password" :error-messages="errors" />
</ValidationProvider>

You can see providers in action here

Refactoring Validation Providers

While the ValidationProvider has its advantages, it is more verbose than using the v-model directive, and can be very annoying when creating large forms. There are a couple of ways to address this issue.

Creating Higher-Order Components

A common pattern in React is to use higher-order components to produce new components with slightly different behavior. This is similar to creating a wrapper or a mixin for our component, except it uses props/events to communicate state.

The withValidation method takes a component as an argument and creates a new one with the validation behavior enabled. Let's create a VTextFieldWithValidation using this method:

import { withValidation } from 'vee-validate';
import { VTextField } from 'vuetify/lib';

const VTextFieldWithValidation = withValidation(VTextField, ({ errors }) => ({
  'error-messages': errors
}));

export default {
  components: {
    VTextFieldWithValidation
  }
};

TIP

Note that the second parameter is a function that transforms the validation context to props object to be passed to the wrapped component. In this case we want to pass the errors array as the error-messages prop to the VTextField component.

With this approach the last example becomes:

<VTextFieldWithValidation rules="required|confirmed:confirm" v-model="password" />

<VTextFieldWithValidation vid="confirm" rules="required" v-model="password" />

WARNING

This approach has some cons, for example if the wrapped component accepts props that have the same name as the ValidationProvider component. while it will receive these props, they may be of different types, which could lead to serious issues. The problem with HOCs are that you need to be aware of the underlying component's implementation. This can be problematic when working with 3rd party components.

Wrapping Components Manually

Instead we can wrap the field component with the ValidationProvider in a new component. This is easier and more flexible, and avoids some of the potential problems with Higher-Order components.

Consider this new VTextFieldWithValidation component.

<template>
  <ValidationProvider :rules="rules" v-slot="{ errors }">
    <VTextField v-model="innerValue" :error-messages="errors" />
  </ValidationProvider>
</template>

<script>
import { ValidationProvider } from 'vee-validate';

export default {
  props: {
    rules: [String],
    value: null
  },
  components: {
    ValidationProvider
  },
  data: () => ({
    innerValue: null
  }),
  watch: {
    innerValue (val) {
      this.$emit('input', val);
    }
  }
};
</script>

Ideally you would pass the props you need to either the ValidationProvider or the VTextField being validated, with this approach solves the verbosity problem while preserving the simple scoped slots API. It also allows you to distribute props without the issues of having a conflict, unlike HOC.

Using either of these approaches is at your preference.

Persisting Provider Errors

Sometimes when building something like a multi-step form, you would need to use v-if on your providers to toggle the visibility of your steps. However, when the provider is hidden and shown again, it does not keep its state.

You can use the persist prop to allow the provider to remember its state across mounting/destroyed lifecycles, but there are a couple of caveats:

  • Your Provider must be inside an observer component.
  • Your Provider must have a vid property set.
<ValidationObserver>
  <div v-if="!isHidden">
    <ValidationProvider
      rules="required|min:3|max:6"
      vid="myfield"
      v-slot="{ errors }"
      :persist="true"
    >
      <input type="text" v-model="value">
      {{ errors[0] }}
    </ValidationProvider>
  </div>
</ValidationObserver>
<button @click="isHidden = !isHidden">Toggle</button>

Adding Errors Manually

You may want to add manual errors to a field, such cases like pre-filling initially due to a server response, or an async request. You can do this using refs and the applyResult method.

<template>
  <div id="app">
    <ValidationProvider ref="provider" rules="required" v-slot="{ errors }">
      <input v-model="model" type="text">
      <div v-if="errors" v-text="errors[0]"></div>
    </ValidationProvider>
  </div>
</template>

<script>
import { ValidationProvider } from "vee-validate";

export default {
  components: {
    ValidationProvider
  },
  mounted() {
    // all those properties are required.
    this.$refs.provider.applyResult({
      errors: ["this is a backend error"], // array of string errors
      valid: false, // boolean state
      failedRules: {} // should be empty since this is a manual error.
    });
  }
};
</script>

TIP

If you are using TypeScript you may face issues with $refs not giving you the correct typings, you can solve that by casting the ref to a ValidationProvider.

(this.$refs.provider as ValidationProvider).applyResult({
  errors: ["this is a backend error"],
  valid: false,
  failedRules: {}
});

Nested Slots

If you want to validate a field inside another component that uses slots, you need to be aware that the ValidationProvider only detects inputs marked by v-model that are within its immediate slot, meaning nested inputs inside other components will not be detected.

For example, this will not be detected.

<ValidationProvider v-slot="{ errors }">
  <MyNestedComponent v-slot="componentSlot">
    <input v-model="value" type="text">
  </MyNestedComponent>
</ValidationProvider>

One way around that is to invert the nesting of the components, for example the above example should be functionally and visually equivalent to this:

<MyNestedComponent v-slot="componentSlot">
  <ValidationProvider v-slot="{ errors }">
    <input v-model="value" type="text">
  </ValidationProvider>
</MyNestedComponent>

But we have placed the input inside the provider slot directly and it should be detected and validated correctly, you can also use the slim prop to make sure everything stays visually the same.

Reference

Below is the reference of the ValidationProvider public API.

Props

All the following props are optional.

Prop Type Default Value Description
rules string | object undefined The validation rules.
vid string auto increment number Identifier used for target/cross-field based rules.
immediate boolean false If the field should be validated immediately after render (initially).
events string[] ['input'] deprecated: check interaction modes
name string undefined A string that will be used to replace {field} in error messages and for custom error messages.
bails boolean true If true, the validation will stop on the first failing rule.
debounce number 0 Debounces the validation for the specified amount of milliseconds.
tag string span The default tag to render.
persist boolean false If true, the provider will keep its errors across mounted/destroyed lifecycles
slim boolean false If true, it will make the provider renderless, only rendering the HTML inside its slot.

Methods

Those are the only methods meant for public usage, other methods that may exist on the ValidationProvider are strictly internal.

Method Args Return Value Description
validate value?: any Promise<ValidationResult> Runs a validation of the current value against the rules defined. If a value is provided, it is used as the current value and validates it.
validateSilent void Promise<ValidationResult> Runs a validation of the current value against the rules defined. does not mutate the validation state.
applyResult ValidationResult void Takes a validation result object and applies it on the current state.
reset void void Resets validation state.
setFlags Object void Updates the field flag states with an object, the object properties should be the flag names and the values should be boolean values.

Events

The validation provider does not emit any events at this time.