Tutorials Logic, IN info@tutorialslogic.com

Vue Forms v model Form Validation

Form State

v-model connects a form control’s displayed value and its reactive source. Different controls use different DOM properties: text inputs use value, checkboxes use checked values, and selects use selected options. The application still owns validation, submission state, and the server response.

v-model - Two-Way Binding

v-model creates a two-way binding between a form input and reactive data. It's shorthand for :value="data" + @input="data = $event.target.value". Vue's v-model is much simpler than React's controlled components.

v-model - All Input Types and Validation

v-model - All Input Types and Validation
<template>
  <form @submit.prevent="handleSubmit">
    <!-- Text input -->
    <input v-model="form.name" placeholder="Name" />

    <!-- Email -->
    <input v-model="form.email" type="email" placeholder="Email" />

    <!-- Number with .number modifier -->
    <input v-model.number="form.age" type="number" />

    <!-- Textarea -->
    <textarea v-model="form.bio" rows="4" />

    <!-- Select -->
    <select v-model="form.country">
      <option value="">Select country</option>
      <option value="us">United States</option>
      <option value="uk">United Kingdom</option>
      <option value="in">India</option>
    </select>

    <!-- Multi-select -->
    <select v-model="form.skills" multiple>
      <option v-for="skill in availableSkills" :key="skill" :value="skill">
        {{ skill }}
      </option>
    </select>

    <!-- Radio buttons -->
    <label v-for="role in roles" :key="role">
      <input type="radio" v-model="form.role" :value="role" />
      {{ role }}
    </label>

    <!-- Checkbox (boolean) -->
    <label>
      <input type="checkbox" v-model="form.newsletter" />
      Subscribe to newsletter
    </label>

    <!-- Checkbox (array) -->
    <label v-for="tag in availableTags" :key="tag">
      <input type="checkbox" v-model="form.tags" :value="tag" />
      {{ tag }}
    </label>

    <button type="submit">Submit</button>
    <pre>{{ JSON.stringify(form, null, 2) }}</pre>
  </form>
</template>

<script setup>
import { reactive } from 'vue'

const form = reactive({
  name: '', email: '', age: 0, bio: '',
  country: '', skills: [], role: 'user',
  newsletter: false, tags: []
})

const availableSkills = ['Vue', 'React', 'Angular', 'Node.js']
const roles = ['user', 'admin', 'moderator']
const availableTags = ['Frontend', 'Backend', 'DevOps', 'Design']

function handleSubmit() {
  console.log('Form submitted:', form)
}
</script>

v-model - Two-Way Binding - HTML Example

v-model - Two-Way Binding - HTML Example
<template>
  <form @submit.prevent="handleSubmit">
    <div class="field">
      <input v-model="form.email" type="email" placeholder="Email"
        :class="{ 'input-error': errors.email }" @blur="validateField('email')" />
      <span v-if="errors.email" class="error">{{ errors.email }}</span>
    </div>

    <div class="field">
      <input v-model="form.password" type="password" placeholder="Password"
        :class="{ 'input-error': errors.password }" @blur="validateField('password')" />
      <span v-if="errors.password" class="error">{{ errors.password }}</span>
    </div>

    <button type="submit" :disabled="!isValid">Login</button>
  </form>
</template>

<script setup>
import { reactive, computed } from 'vue'

const form = reactive({ email: '', password: '' })
const errors = reactive({ email: '', password: '' })

function validateField(field) {
  if (field === 'email') {
    if (!form.email) errors.email = 'Email is required'
    else if (!/\S+@\S+\.\S+/.test(form.email)) errors.email = 'Invalid email'
    else errors.email = ''
  }
  if (field === 'password') {
    if (!form.password) errors.password = 'Password is required'
    else if (form.password.length < 8) errors.password = 'Min 8 characters'
    else errors.password = ''
  }
}

const isValid = computed(() =>
  form.email && form.password && !errors.email && !errors.password
)

function handleSubmit() {
  validateField('email')
  validateField('password')
  if (isValid.value) alert('Login successful!')
}
</script>

Input Conversion

Browser input values are strings by default. Use the .number modifier only when its empty and invalid-value behavior fits the field, or parse explicitly at validation time. .trim removes surrounding whitespace, while .lazy updates on change rather than each input event. Keep the raw draft when users need to correct incomplete values.

Associate labels with controls, expose field errors accessibly, and do not clear values after server validation fails. Disable duplicate submission while one write is active, but avoid disabling the whole form when only one dependent choice is loading. Treat server validation as authoritative.

Controls, Modifiers, and Value Shapes

v-model maps to different DOM properties and events by control type. Text inputs and textareas bind strings, a single checkbox normally binds a Boolean, checkbox groups bind an array, radio buttons bind the selected value, and select multiple binds an array. Initialize state with the same shape the control will produce.

Use .trim when surrounding whitespace is not meaningful, .number when a numeric conversion matches the domain, and .lazy when updates should occur on change instead of every input event. Number conversion can still produce an empty string, so validation must handle empty and invalid states explicitly.

Bind several control shapes

Bind several control shapes
<script setup>
import { reactive } from 'vue'
const form = reactive({ name: '', age: null, skills: [], plan: 'free' })
</script>

<template>
  <input v-model.trim="form.name" name="name">
  <input v-model.number="form.age" name="age" type="number">
  <label><input v-model="form.skills" type="checkbox" value="vue"> Vue</label>
  <select v-model="form.plan" name="plan">
    <option value="free">Free</option>
    <option value="pro">Pro</option>
  </select>
</template>

Each field starts with the value shape produced by its control, avoiding Boolean, string, and array confusion.

Validation, Submission, and Accessibility

Client validation improves feedback but does not establish trust; validate the same rules on the server. On submit, mark attempted fields, focus the first invalid control, prevent duplicate submissions while a request is pending, and preserve user input when the server rejects the request.

Every control needs a persistent label. Connect help and error text with aria-describedby, set aria-invalid only when invalid, and announce submission-level failures without moving focus unpredictably. Do not disable the submit button in a way that prevents keyboard users from discovering why submission is blocked.

  • Store field errors by stable field name, not by visual position.
  • Distinguish validation errors from network and server failures.
  • Abort or ignore stale responses when a newer submission supersedes them.
  • Never render raw server error HTML inside the form.
Before you move on

Vue Forms v model Form Validation Mastery Check

3 checks
  • v-model creates a two-way binding between a form input and reactive data.
  • It's shorthand for :value="data" + @input="data = $event.target.value".
  • Vue's v-model is much simpler than React's controlled components.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.