Vue Forms

The v-model directive creates a two-way binding between a form control and component data, keeping text fields, checkboxes, and select menus synchronized with state automatically.

Two-Way Binding with v-model

Normally, data flows one way in Vue: from data to the template. v-model breaks that in a controlled way for form elements, listening for the right input event and updating the bound property automatically, while also feeding the current value of that property back into the element. Type into the field and the data updates; change the data and the field updates.

A text input bound with v-model

<div id="app">
  <input v-model="username" placeholder="Enter a username" />
  <p>Hello, {{ username || 'stranger' }}!</p>
</div>

<script>
const app = Vue.createApp({
  data() {
    return { username: '' }
  }
})
app.mount('#app')
</script>

Checkboxes and Radio Buttons

A single checkbox with v-model binds to a boolean. Multiple checkboxes sharing the same v-model array will push their value into the array when checked and remove it when unchecked. Radio buttons sharing the same v-model bind to whichever single value was last selected.

Checkboxes bound to an array, plus radio buttons

<div id="app">
  <p>Toppings:</p>
  <label><input type="checkbox" value="cheese" v-model="toppings" /> Cheese</label>
  <label><input type="checkbox" value="olives" v-model="toppings" /> Olives</label>
  <label><input type="checkbox" value="mushrooms" v-model="toppings" /> Mushrooms</label>

  <p>Size:</p>
  <label><input type="radio" value="small" v-model="size" /> Small</label>
  <label><input type="radio" value="large" v-model="size" /> Large</label>

  <p>Selected: {{ toppings.join(', ') || 'none' }}, size {{ size }}</p>
</div>

<script>
const app = Vue.createApp({
  data() {
    return {
      toppings: [],
      size: 'small'
    }
  }
})
app.mount('#app')
</script>
  • .lazy - syncs on the change event instead of every keystroke's input event
  • .number - automatically casts the input's string value to a number
  • .trim - strips leading and trailing whitespace from the value automatically
  • v-model on a checkbox array adds or removes matching values as boxes are toggled
  • v-model works the same way across input, textarea, and select elements
Note: By default, a text input updates on every single keystroke since it listens to the input event. Add .lazy — as in v-model.lazy="username" — if you'd rather wait until the field loses focus or the user hits Enter.

Select Dropdowns

A single select bound with v-model works like a radio group: whichever option's value matches the bound data is shown as selected, and choosing a new option updates the data. Adding the multiple attribute to the select element switches the binding to an array, letting the user pick more than one option at once.

ElementBound data type
<input type="text">string
<input type="checkbox"> (single)boolean
<input type="checkbox"> (grouped)array
<input type="radio"> (grouped)string or number
<select>the value of the chosen option
<select multiple>array of chosen option values

A select dropdown built with v-for

<div id="app">
  <select v-model="plan">
    <option v-for="option in plans" :key="option" :value="option">
      {{ option }}
    </option>
  </select>
  <p>You picked: {{ plan }}</p>
</div>

<script>
const app = Vue.createApp({
  data() {
    return {
      plans: ['Free', 'Starter', 'Pro', 'Enterprise'],
      plan: 'Starter'
    }
  }
})
app.mount('#app')
</script>
Note: v-model is really shorthand for binding :value and listening for the corresponding update event; it isn't magic specific to native inputs. Custom components can support v-model too, as long as they accept a value prop and emit the matching update event, which is covered separately once you get to building your own components.

Exercise: Vue Forms

What does v-model on a form input primarily provide?