Two-Way Data Binding in Vue.js Using V-Model


· ·

Vue.js is a progressive JavaScript framework used to build interactive web interfaces. One of the key benefits of Vue.js is its ability to provide a very simple reactive two-way data binding for custom form inputs and vue components. In this article, I’ll explain and demonstrate how Vue.js binds form input data using the V-model directive. 

What is Two-Way Data Binding?

Vue.js uses the V-model directive to bind data. Given a form input, V-model listens to any input events or updates on the view. It then updates the data model property with the new value. It also updates the view template if there is a change in the data model.  This is called two-way data binding. In other words, two-way data binding involves two steps: 

  1. When the data model properties get updated, the UI elements to get updated. 
  2. When UI elements get updated, the same changes are propagated back to the data model property almost immediately. 

V-model has some caveats regarding different properties and emits different events for form input elements:

  • text and textarea elements use the value attribute and input event
  • checkboxes and radio buttons use checked property and change event
  • select fields use value attribute and change as an event

Demonstrating Vue.js Two-Way Data Binding

I will build a simple registration form with a preview section to demonstrate how V-model directive binds form data with using input, checkbox, radio button, select and textarea elements. 


This tutorial is suited for novice and intermediate users who want to further their understanding of the v-model directive, and I am assuming a general basic knowledge of Vue as a whole. Other requirements include: 

  • Have Node.js version 10.x and above installed.  Verify this by running this command in the terminal:
  • Have Vue and Vue CLI 3.0 installed 
  • Code Editor such as Visual Studio Code
Setting up the Vue Project
  • Download a Vue starter project from github
  • Unzip the downloaded folder
  • Navigate into the unzipped file and run the command below to keep all the dependencies up to date

npm install

  • Start by opening the folder in the code editor. 

Inside the Vue components folder, there are two components: app.vue and test.vue. The  app.vue component is the root Vue component that instantiates the app by displaying the Vue logo and the contents of the text.vue component. It also contains a CSS style section that defines how the overall app template looks. The app.vue  file should look like this:


  <div id="app">

    <img alt="Vue logo" src="./assets/logo.png" style="width:100px; height:100px;">

    <Test msg="Welcome to Your Vue.js App"/>




import Test from './components/Test.vue'

export default {

  name: 'app',

  components: {










   font-family: 'Avenir', Helvetica, Arial, sans-serif;

  -webkit-font-smoothing: antialiased;

  -moz-osx-font-smoothing: grayscale;

  color: #2c3e50;

  margin-top: 60px;

  display: inline-block;




Inside the Test.Vue file, I define my registration form within the template section. The form will follow the basic HTML5 syntax for form inputs, but will contain the V-model directive as part of the input section to ensure input-binding.

For example, the first name field would look like this:

<input type="text" v-model="fName" required>

As the user types within the field, the V-model directive binds the data being entered as an fName  property value which can then be previewed on change.This way, he or she can validate the value before clicking the submit button. 

To preview the value of the data being entered, the data property within the script is instructed to return the value of the V-model like:


export default {



    return {

      fName: ''

. . .


The value being returned by the data property is then sent back to the UI to be viewed as:

<p>Name: {{fName}} </p>


To fully see how V-model directive binds all the data within the form, the final contents of the test.vue file should look like:


  <div id="form_template">

    <h1>Vue User Registration Form</h1>                    

    <form style="align-content: left; width: 40%">


        <label for="first name">First Name:</label>

        <input type="text" v-model="fName" required>



        <label for="last name">Last Name:</label>

        <input type="text" v-model="lname" required>



        <label for="age">Age:</label>

        <input type="number" v-model="age" required>



        <label for="enthinicy">Ethnicity:</label>

        <select v-model="enthinicy">

          <option disabled value="">Select One</option>



          <option>Black or African American</option>

          <option>American Indian or Alaska Native</option>

          <option>Hispanic or Latino</option>

          <option>Native Hawaiian or Other Pacific Islander</option>



        <label for="gender">Gender:</label>

        <div id="radioinput">

          <input type="radio" value="Male" v-model="gender" />

            <label for="gender"><span></span>Female</label>

            <br />

            <input type="radio" value="Female" v-model="gender" />

            <label for="gender"><span></span>Male</label>                       



        <label for="description">Description:</label>

        <textarea v-model="description"></textarea> 



      <h2>Form Data Preview</h2>]


        <div id="previewdata">

    <p>Name: {{fName}} {{lname}}</p>

    <p>Age: {{age}} </p>

    <p>Gender: {{gender}}</p>

    <p>enthinicy:  {{enthinicy}}</p>

    <p>Description:  {{ description }}</p>






export default {

  name: 'Test',

  props: {

    msg: String



    return {

      fName: '',

      lname: '',

      gender: '',

      enthinicy: '',

      description: '',

      age: ''





<!-- Add "scoped" attribute to limit CSS to this component only -->

<style scoped>


  display: block;

  margin: 20px 0 10px;

  font-weight: bold;


input[type=text],input[type=number],textarea,select {

  font-size: 30px;

  border: 1px double rgb(102, 97, 96) ;

  border-radius: 4px;

  width: 180%;


#radioinput input[type="radio"] + label{

  display: inline-block;

    padding: 2px;

      margin:0 5px;



  color: green;

  font-size: 15px;





At this point, my project is complete and I can start the server by issuing the command below in the project terminal.

npm run serve


Upon opening the project URL on my browser, this is how it looks:

Vue user registration form

This GIF shows a live preview of the page as the user fills the form:

V-model Modifiers

  • v-model.lazy – This modifier ensures that the V-model listens to the change event after the user is done filling in the field and has unfocused the field, rather than evaluating live input change.
  • v-model.number – This modifier casts valid input strings to numbers
  • v-model.trim – This strips the leading or trailing whitespace from the bound string. 


The V-model  directive has different ways of binding data either from form inputs or from custom components. Check out this reference on V-model on custom components in the official documentation


Find the entire code here

Faith Kilonzi is a full-stack software engineer, technical writer, and a DevOps enthusiast, with a passion for problem-solving through implementation of high-quality software products. She holds a bachelor’s degree in Computer Science from Ashesi University. She has experience working in academia, fin-tech, healthcare, research, technology, and consultancy industries in both Kenya, Ghana, and in the USA. Driven by intellectual curiosity, she combines her passion for teaching, technology, and research to create technical digital content.


Click on a tab to select how you'd like to leave your comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Skip to toolbar