Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 48

Two-Way Binding with v-model

Recommended resources

Hello! Welcome back to our Vue.js module.

In our last lesson, you did an excellent job learning Vue's core directives (v-for, v-on, v-bind, v-if). You saw how they combine to create dynamic user interfaces, and we briefly used v-model to add new tasks to our list. We noted it was a shortcut for a common pattern, which is exactly the kind of "why" we want to explore.

Today's lesson is dedicated entirely to mastering v-model. We'll go beyond that simple text input and unpack how this powerful directive provides two-way data binding. You will learn to implement it on a variety of form inputs, a fundamental skill for building any interactive web application, including the to-do list we're working on and the contact form for your upcoming profile site project.

What is Two-Way Data Binding?

In the previous lesson, you saw how v-bind creates one-way data binding: data flows from your script to the template. If you change a ref in your script, the HTML updates.

Two-way data binding creates a complete loop.

  1. Data flows from the script to the template (like v-bind).
  2. Data also flows from the template back to the script. When a user types in an input field, the ref in your script is updated automatically.

The UI and your application's state are always in sync. The primary tool Vue gives us for this is the v-model directive.

For a clear explanation of what v-model does and, more importantly, how it works under the hood, let's watch a short video.

Vue 3 V-Model Simplified

This video from John Komarnicki provides a fantastic, concise explanation of v-model. The first part breaks down the 'syntactic sugar' aspect, showing you exactly what Vue does for you behind the scenes.

Watch from the beginning until 01:16. Focus on how v-model is simply a shortcut for combining a v-bind:value and a v-on:input event.

As the video explains, writing v-model="myRef" on a text input is equivalent to writing:

<input :value="myRef" @input="event => myRef = event.target.value">

v-model simply saves you from writing this boilerplate code every time. This is a perfect example of Vue's philosophy: providing simple, elegant solutions for common developer tasks while still allowing you to understand the underlying mechanics.


Using v-model on Different Form Inputs

The real power of v-model is that it intelligently adapts to different types of form controls. While the underlying principle of binding a value and listening for an event is the same, the specific attributes and events it uses can change.

Let's continue with the video you just started to see how this works in practice.

Vue 3 V-Model Simplified

This next segment of the video demonstrates how v-model behaves with checkboxes, radio buttons, and select dropdowns.

Watch from 01:16 to 02:46. Pay attention to what kind of data each input type binds to (e.g., a boolean for a single checkbox, a string for a radio button, etc.).


Hands-On Practice: Building a Settings Form

Now it's time to apply what you've just seen. Let's create a simple "User Settings" form to practice using v-model with various inputs.

Open your src/App.vue file, clear its contents, and paste in the following starter code. Below the code, I've added comments that act as your to-do list to complete the exercise.

Starter Code:

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

// Data for our form
const username = ref('J.D')
const notifications = ref(true)
const theme = ref('dark') // 'light' or 'dark'
const interests = ref(['vue']) // can be 'vue', 'tailwind', 'design'
const skillLevel = ref('intermediate')
</script>

<template>
  <div class="form-container">
    <h2>User Settings</h2>
    
    <!-- 1. Text Input: Bind this input to the 'username' ref. -->
    <div class="form-group">
      <label for="username">Username</label>
      <input type="text" id="username">
    </div>

    <!-- 2. Single Checkbox: Bind this to the 'notifications' boolean ref. -->
    <div class="form-group">
      <label>
        <input type="checkbox">
        Enable Notifications
      </label>
    </div>

    <!-- 3. Radio Buttons: Bind these to the 'theme' ref. The value should be 'light' or 'dark'. -->
    <div class="form-group">
      <p>Theme:</p>
      <label><input type="radio" name="theme" value="light"> Light</label>
      <label><input type="radio" name="theme" value="dark"> Dark</label>
    </div>

    <!-- 4. Multiple Checkboxes: Bind these to the 'interests' array. -->
    <div class="form-group">
      <p>Interests:</p>
      <label><input type="checkbox" value="vue"> Vue</label>
      <label><input type="checkbox" value="tailwind"> Tailwind CSS</label>
      <label><input type="checkbox" value="design"> Graphic Design</label>
    </div>

    <!-- 5. Select Dropdown: Bind this to the 'skillLevel' ref. -->
    <div class="form-group">
      <label for="skill">Skill Level</label>
      <select id="skill">
        <option value="beginner">Beginner</option>
        <option value="intermediate">Intermediate</option>
        <option value="advanced">Advanced</option>
      </select>
    </div>

    <hr>
    
    <div class="debug">
      <h3>Live State:</h3>
      <p><strong>Username:</strong> {{ username }}</p>
      <p><strong>Notifications:</strong> {{ notifications }}</p>
      <p><strong>Theme:</strong> {{ theme }}</p>
      <p><strong>Interests:</strong> {{ interests }}</p>
      <p><strong>Skill Level:</strong> {{ skillLevel }}</p>
    </div>
  </div>
</template>

<style>
/* Basic styling for readability */
body { font-family: sans-serif; background: #f0f0f0; }
.form-container { max-width: 500px; margin: 2rem auto; padding: 2rem; background: white; border-radius: 8px; }
.form-group { margin-bottom: 1rem; }
label { margin-right: 1rem; }
input[type="text"], select { width: 100%; padding: 8px; box-sizing: border-box; }
.debug { background: #eee; padding: 1rem; border-radius: 4px; margin-top: 2rem; }
hr { border: 1px solid #eee; margin: 2rem 0; }
</style>

Your task is to add the v-model directive to each of the five form controls as indicated by the comments. The "Live State" section at the bottom will show you in real-time if your bindings are working correctly.

Take a few minutes to complete this. Once you're done, you can check your work against the solution below.

Click to see the completed template
<template>
  <div class="form-container">
    <h2>User Settings</h2>
    
    <div class="form-group">
      <label for="username">Username</label>
      <input type="text" id="username" v-model="username">
    </div>

    <div class="form-group">
      <label>
        <input type="checkbox" v-model="notifications">
        Enable Notifications
      </label>
    </div>

    <div class="form-group">
      <p>Theme:</p>
      <label><input type="radio" name="theme" value="light" v-model="theme"> Light</label>
      <label><input type="radio" name="theme" value="dark" v-model="theme"> Dark</label>
    </div>

    <div class="form-group">
      <p>Interests:</p>
      <label><input type="checkbox" value="vue" v-model="interests"> Vue</label>
      <label><input type="checkbox" value="tailwind" v-model="interests"> Tailwind CSS</label>
      <label><input type="checkbox" value="design" v-model="interests"> Graphic Design</label>
    </div>

    <div class="form-group">
      <label for="skill">Skill Level</label>
      <select id="skill" v-model="skillLevel">
        <option value="beginner">Beginner</option>
        <option value="intermediate">Intermediate</option>
        <option value="advanced">Advanced</option>
      </select>
    </div>

    <hr>
    
    <div class="debug">
      <h3>Live State:</h3>
      <p><strong>Username:</strong> {{ username }}</p>
      <p><strong>Notifications:</strong> {{ notifications }}</p>
      <p><strong>Theme:</strong> {{ theme }}</p>
      <p><strong>Interests:</strong> {{ interests }}</p>
      <p><strong>Skill Level:</strong> {{ skillLevel }}</p>
    </div>
  </div>
</template>

Refining v-model with Modifiers

Sometimes, the default behavior of v-model isn't quite what you need. For example, you might want to automatically trim whitespace from a user's input, or ensure an input is treated as a number instead of a string. For these cases, v-model has modifiers.

How does v-model handle data binding?

The LogRocket article 'Using v-model in Vue 3 to build complex forms' has a great section that explains the most common modifiers. It's a quick read that will add some precision tools to your v-model toolkit.

Read the section titled 'How does v-model handle data binding?'. Focus on understanding what the .lazy, .number, and .trim modifiers do.

To summarize the reading:

  • .trim: Automatically trims leading and trailing whitespace.
    • v-model.trim="username"
  • .number: Attempts to convert the input value to a number. If it fails, it returns the original string.
    • v-model.number="age"
  • .lazy: Syncs the state after a change event (when the user clicks away from the input) instead of every input event (on every keystroke).
    • v-model.lazy="searchQuery"

Let's apply one of these to our form. It's good practice to trim username inputs. Find the text input for the username and add the .trim modifier:

<input type="text" id="username" v-model.trim="username">

Now, if you type " J.D " (with spaces) into the username field and then click away, you will see the username state in the debug area update to "J.D" without the extra spaces.


Conclusion

Great work today! You have now taken a deep dive into one of Vue's most fundamental and useful directives. You've not only learned how to use it but also why it works, which is crucial for demystifying the framework.

Key Takeaways:

  • Two-Way Data Binding: v-model creates a synchronized link between your component's state and a form input.
  • Syntactic Sugar: v-model is a convenient shortcut for binding an input's value and listening for an event to update the state.
  • Versatility: v-model works intelligently with text inputs, checkboxes (single and multiple), radio buttons, and select dropdowns.
  • Modifiers: You can refine v-model's behavior with modifiers like .trim, .number, and .lazy to handle common edge cases elegantly.

In our next lesson, we will explore computed properties. These are a powerful feature for creating new reactive values that are derived from your existing state—for example, calculating the number of remaining tasks in our to-do list. This will be another important step towards managing application state cleanly and efficiently.