Skip to content

v-model

The v-model directive lets you create a two-way binding on a form input element or a component. You can use it with:

  • <input>
  • <textarea>
  • <select>
  • Components
vue
<input v-model="text" />

v-model automatically expands to different DOM property and event pairs based on the element it is used on:

  • <input> with text types and <textarea> elements use value property and input event.
  • <input> with checkbox and radio types use checked property and change event.
  • <select> elements use value property and change event.

This abstraction allows you to write consistent, declarative code without worrying about the specific event handling logic for each input type.

WARNING

v-model will ignore the initial value, checked or selected attributes found on any form elements. It will always treat the current bound JavaScript state as the source of truth.

Basic Usage

Text

vue
<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />

Multiline Text

vue
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

Checkbox

Single checkbox (boolean value):

vue
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>

You can bind multiple checkboxes to the same array (or a Set in Vue 3.3+):

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

const checkedNames = ref([])
</script>

<template>
  <div>Checked names: {{ checkedNames }}</div>

  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
  <label for="jack">Jack</label>

  <input type="checkbox" id="john" value="John" v-model="checkedNames" />
  <label for="john">John</label>

  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
  <label for="mike">Mike</label>
</template>

In this example, the checkedNames array always reflects the values of the currently checked boxes.

Radio

vue
<div>Picked: {{ picked }}</div>

<input type="radio" name="picked" id="one" value="One" v-model="picked" />
<label for="one">One</label>

<input type="radio" name="picked" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>

Radio buttons sharing the same v-model will update the bound value based on the selected option.

Select

Single Select:

vue
<div>Selected: {{ selected }}</div>

<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

Multiple Select (bound to array):

vue
<div>Selected: {{ selected }}</div>

<select v-model="selected" multiple>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

Select options can be dynamically rendered with v-for:

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

const selected = ref('A')
const options = ref([
  { text: 'One', value: 'A' },
  { text: 'Two', value: 'B' },
  { text: 'Three', value: 'C' }
])
</script>

<template>
  <div>Selected: {{ selected }}</div>

  <select v-model="selected">
    <option v-for="option in options" :value="option.value" :key="option.value">
      {{ option.text }}
    </option>
  </select>
</template>

Value Bindings

For radio, checkbox and select options, the v-model binding values are usually static strings (or booleans for checkboxes):

vue
<!-- `picked` is "a" when checked -->
<input type="radio" v-model="picked" value="a" />

<!-- `toggle` is true or false -->
<input type="checkbox" v-model="toggle" />

<!-- `selected` is "abc" when the first option is selected -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>

But sometimes we may want to bind dynamic or non-string values. v-bind can be used to achieve that.

Checkbox

Vue provides true-value and false-value attributes for checkboxes:

vue
<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no" />

When checked, toggle becomes "yes"; when unchecked, it becomes "no".

You can also bind these values dynamically using v-bind:

vue
<input
  type="checkbox"
  v-model="toggle"
  :true-value="dynamicTrueValue"
  :false-value="dynamicFalseValue" />

Radio

vue
<input type="radio" name="picked" v-model="pick" :value="first" />
<input type="radio" name="picked" v-model="pick" :value="second" />

pick will be set to the value of first when the first radio input is checked, and set to the value of second when the second one is checked.

Select

vue
<select v-model="selected">
  <!-- inline object literal -->
  <option :value="{ number: 123 }">123</option>
</select>

v-model supports binding non-string values. In the above example, when the option is selected, selected will be set to the object { number: 123 }.

Modifiers

.lazy

By default, v-model syncs the input with the data on every input event (on every keystroke). Use .lazy modifier to sync only after the change event (when the input loses focus):

vue
<!-- synced after "change" instead of "input" -->
<input v-model.lazy="msg" />

.number

The number modifier can be used to automatically typecast user input as a number:

vue
<input v-model.number="age" />

If the value cannot be parsed with parseFloat(), then the original (string) value is used instead. In particular, if the input is empty (for instance after the user clearing the input field), an empty string is returned.

NOTE

The number modifier is applied automatically if the input has type="number".

.trim

The trim modifier can be used to automatically trim whitespace from user input:

vue
<input v-model.trim="msg" />

With Components

HTML's built-in input types won't always meet your needs. Fortunately, Vue Components let you build reusable, fully customized input controls and they integrate seamlessly with v-model.

By implementing the appropriate props and events, a component can behave just like a native form element while encapsulating its own logic and presentation.

Component usage with v-model is covered in detail in a later guide.