Skip to main content
Vue.js for Beginners to Advanced
CHAPTER 11 Beginner

Forms and Input Handling

Updated: May 18, 2026
5 min read

# CHAPTER 11

Forms and Input Handling

1. Chapter Introduction

Forms are the most critical user interaction surface in any application. Vue's v-model directive provides two-way data binding for every input type. Combined with reactive validation, forms in Vue are clean, declarative, and powerful.

2. Learning Objectives

  • Bind all form elements with v-model.
  • Implement reactive form validation.
  • Use input modifiers (.trim, .lazy, .number).
  • Handle file inputs and dynamic forms.
  • Build a complete registration form with validation.

3. All Form Controls with v-model

vue
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
<script setup>
import { ref } from &#039;vue'

const form = ref({
  name: &#039;',
  email: &#039;',
  password: &#039;',
  age: null,
  bio: &#039;',
  role: &#039;',
  skills: [],
  newsletter: false,
  notifications: &#039;email',
  avatar: null,
  rating: 0
})
</script>

<template>
  <form @submit.prevent="submitForm">
    <!-- Text input -->
    <input type="text" v-model.trim="form.name" placeholder="Full Name" />

    <!-- Email -->
    <input type="email" v-model.lazy="form.email" placeholder="Email (updates on blur)" />

    <!-- Password -->
    <input type="password" v-model="form.password" placeholder="Password" />

    <!-- Number (converts string to number) -->
    <input type="number" v-model.number="form.age" placeholder="Age" />

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

    <!-- Select -->
    <select v-model="form.role">
      <option value="">Select Role</option>
      <option value="developer">Developer</option>
      <option value="designer">Designer</option>
      <option value="manager">Manager</option>
    </select>

    <!-- Multiple checkboxes → array -->
    <label><input type="checkbox" v-model="form.skills" value="Vue" /> Vue</label>
    <label><input type="checkbox" v-model="form.skills" value="React" /> React</label>
    <label><input type="checkbox" v-model="form.skills" value="Angular" /> Angular</label>
    <p>Skills: {{ form.skills.join(&#039;, ') }}</p>

    <!-- Single checkbox → boolean -->
    <label><input type="checkbox" v-model="form.newsletter" /> Subscribe to newsletter</label>

    <!-- Radio buttons -->
    <label><input type="radio" v-model="form.notifications" value="email" /> Email</label>
    <label><input type="radio" v-model="form.notifications" value="sms" /> SMS</label>
    <label><input type="radio" v-model="form.notifications" value="none" /> None</label>

    <!-- Range slider -->
    <input type="range" v-model.number="form.rating" min="0" max="10" />
    <span>Rating: {{ form.rating }}/10</span>

    <button type="submit">Submit</button>
  </form>
</template>

4. Reactive Form Validation

vue
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
<script setup>
import { ref, computed } from &#039;vue'

const form = ref({ name: &#039;', email: '', password: '', confirmPassword: '' })
const submitted = ref(false)

// Computed validation errors
const errors = computed(() => {
  const e = {}
  if (!form.value.name.trim()) e.name = &#039;Name is required'
  else if (form.value.name.length < 2) e.name = &#039;Name must be at least 2 characters'

  if (!form.value.email) e.email = &#039;Email is required'
  else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.value.email)) e.email = &#039;Invalid email format'

  if (!form.value.password) e.password = &#039;Password is required'
  else if (form.value.password.length < 8) e.password = &#039;Password must be at least 8 characters'
  else if (!/[A-Z]/.test(form.value.password)) e.password = &#039;Must contain at least one uppercase letter'

  if (form.value.confirmPassword !== form.value.password) e.confirmPassword = &#039;Passwords do not match'

  return e
})

const isValid = computed(() => Object.keys(errors.value).length === 0)

const showError = (field) => submitted.value && errors.value[field]

function submitForm() {
  submitted.value = true
  if (isValid.value) {
    console.log(&#039;Form submitted:', form.value)
    alert(&#039;Registration successful!')
  }
}
</script>

<template>
  <div class="form-wrapper">
    <h2>Create Account</h2>
    <form @submit.prevent="submitForm" novalidate>
      <div class="field">
        <label>Full Name</label>
        <input
          type="text"
          v-model.trim="form.name"
          :class="{ error: showError(&#039;name') }"
          placeholder="Alice Johnson"
        />
        <span v-if="showError(&#039;name')" class="error-msg">{{ errors.name }}</span>
      </div>

      <div class="field">
        <label>Email</label>
        <input
          type="email"
          v-model.lazy="form.email"
          :class="{ error: showError(&#039;email') }"
          placeholder="alice@example.com"
        />
        <span v-if="showError(&#039;email')" class="error-msg">{{ errors.email }}</span>
      </div>

      <div class="field">
        <label>Password</label>
        <input
          type="password"
          v-model="form.password"
          :class="{ error: showError(&#039;password') }"
        />
        <span v-if="showError(&#039;password')" class="error-msg">{{ errors.password }}</span>

        <!-- Password strength indicator -->
        <div class="strength-bar">
          <div :class="[&#039;strength', strengthLevel]" :style="{ width: strengthWidth }"></div>
        </div>
      </div>

      <div class="field">
        <label>Confirm Password</label>
        <input
          type="password"
          v-model="form.confirmPassword"
          :class="{ error: showError(&#039;confirmPassword') }"
        />
        <span v-if="showError(&#039;confirmPassword')" class="error-msg">{{ errors.confirmPassword }}</span>
      </div>

      <button type="submit" :disabled="submitted && !isValid" class="submit-btn">
        Create Account
      </button>
    </form>
  </div>
</template>

5. Common Mistakes

  • Not using .number modifier: v-model on type="number" still returns a string by default. Use v-model.number to get an actual number.
  • Validating on every keystroke: Use v-model.lazy for expensive validations (API calls) so they only trigger on blur.

6. MCQs

Question 1

v-model.trim does?

Question 2

Multiple checkboxes bound to v-model need?

Question 3

v-model.lazy updates on?

Question 4

Best way to track form validity?

Question 5

File input with v-model?

Question 6

novalidate on form?

Question 7

Radio group binding?

Question 8

Dynamic form fields added at runtime?

Question 9

v-model.number on type="number" input?

Question 10

@submit.prevent on form is essential because?

7. Interview Questions

  • Q: How do you implement real-time form validation in Vue 3?
  • Q: What is the difference between v-model.lazy and v-model?

8. Summary

Vue's v-model provides seamless two-way binding for every HTML form element. Reactive computed validation gives real-time error feedback without libraries. The pattern of errors computed + showError(field) function creates a production-quality validation system with minimal code.

9. Next Chapter Recommendation

In Chapter 12: Computed Properties and Watchers, we master the two most powerful reactive programming tools in Vue — computed properties for derived state and watchers for side effects.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·