Skip to content

v-model and Two-Way Binding

v-model creates two-way binding between a form input and a reactive variable. When the user types, the variable updates. When the variable changes programmatically, the input reflects the new value.

<script setup lang="ts">
import { ref } from 'vue'
const name = ref('')
const birthYear = ref<number | ''>('')
const isDeceased = ref(false)
const relationship = ref('parent')
</script>
<template>
<!-- Text input -->
<input v-model="name" type="text" placeholder="Full name" />
<p>Name: {{ name }}</p>
<!-- Number input -->
<input v-model="birthYear" type="number" />
<!-- Checkbox -->
<input v-model="isDeceased" type="checkbox" />
<label>Deceased</label>
<!-- Select -->
<select v-model="relationship">
<option value="parent">Parent</option>
<option value="child">Child</option>
<option value="spouse">Spouse</option>
</select>
</template>

v-model is shorthand for binding the value and listening to the input event simultaneously:

<!-- These two are equivalent -->
<input v-model="name" />
<input :value="name" @input="name = $event.target.value" />

Strips leading/trailing whitespace automatically:

<input v-model.trim="name" />

Converts the input value to a number:

<input v-model.number="birthYear" type="number" />

Updates on change instead of input (syncs after the user leaves the field):

<input v-model.lazy="bio" />

v-model on custom components with defineModel

Section titled “v-model on custom components with defineModel”

Vue 3.4+ introduced defineModel — a macro that creates a two-way binding prop for custom components:

AppInput.vue
<script setup lang="ts">
const model = defineModel<string>()
</script>
<template>
<input :value="model" @input="model = $event.target.value" />
</template>

The parent uses it exactly like a native input:

<AppInput v-model="personName" />

PersonForm.vue uses v-model for every field in the add/edit form:

<input id="pf-name" v-model="name" type="text" placeholder="Full name" required />
<input id="pf-birth" v-model="birthYear" type="number" placeholder="e.g. 1952" />
<textarea id="pf-bio" v-model="bio" placeholder="A few notes (optional)" />

Each field is bound to a local ref. On submit, the refs are collected into a PersonFormData object and emitted to the parent.

  1. Create a simple person form with v-model bindings for name, birth year, and a bio textarea.
  2. Display the current values live below the form as you type.
  3. Add a “Clear” button that resets all fields to empty strings.
  4. Add .trim to the name input and verify that whitespace is stripped automatically.
  • v-model="ref" creates two-way binding between an input and a reactive variable.
  • Works on text inputs, number inputs, checkboxes, selects, and textareas.
  • Modifiers: .trim, .number, .lazy.
  • On custom components, use defineModel() to create a two-way bindable prop.
  • v-model is shorthand for :value + @input — understanding the longhand helps when debugging.