v-model creates a two-way binding between a form control and a piece of state, but each control type stores its value differently: a text input holds a string, a checkbox holds a boolean or an array, a <select multiple> holds an array. In this lesson you will bind every native control correctly, use the .lazy, .number and .trim modifiers, and customize the values a checkbox or radio writes into your state.
For a text input, v-model="name" is equivalent to binding :value="name" and listening for @input="name = $event.target.value". Vue picks the right property and event for each element type automatically:
| Element | Property bound | Event listened to |
| --- | --- | --- |
| <input type="text">, <textarea> | value | input |
| <input type="checkbox">, <input type="radio"> | checked | change |
| <select> | value | change |
Because v-model sets the value from state, an initial value="" or selected attribute in the template is ignored. Initialize the state instead.
<script setup>
import { ref } from 'vue'
const message = ref('')
const accepted = ref(false)
const interests = ref([])
const plan = ref('free')
const tags = ref([])
</script>
<template>
<textarea v-model="message"></textarea>
<label><input type="checkbox" v-model="accepted" /> I accept</label>
<label><input type="checkbox" value="vue" v-model="interests" /> Vue</label>
<label><input type="checkbox" value="ts" v-model="interests" /> TypeScript</label>
<label><input type="radio" value="free" v-model="plan" /> Free</label>
<label><input type="radio" value="pro" v-model="plan" /> Pro</label>
<select v-model="tags" multiple>
<option value="a">Alpha</option>
<option value="b">Beta</option>
</select>
</template>Rules to remember:
true/false.value from it: checking both gives ['vue', 'ts'].value of the selected option.<select multiple> needs an array; a single <select> stores one value. Provide a disabled placeholder option (<option disabled value="">Choose</option>) when the initial value does not match any option, otherwise iOS Safari cannot fire a change event for the first option.Interpolation inside <textarea>{{ message }}</textarea> does not work; always use v-model.
By default a checkbox writes true/false and radios write the string in value. Use true-value/false-value on checkboxes and :value on radios and options to store other values, including objects:
<template>
<input type="checkbox" v-model="status" true-value="active" false-value="inactive" />
<input type="radio" v-model="selected" :value="{ id: 1, name: 'Basic' }" />
<select v-model="chosen">
<option v-for="opt in options" :key="opt.id" :value="opt">{{ opt.name }}</option>
</select>
</template>true-value and false-value only affect the bound state; the checkbox still submits nothing when unchecked in a plain HTML form submission.
Modifiers are appended to v-model with a dot and change how the value is synced:
<template>
<!-- update after change instead of on every keystroke -->
<input v-model.lazy="search" />
<!-- cast to a number with parseFloat; keeps the string if it cannot be parsed -->
<input v-model.number="quantity" type="number" />
<!-- strip leading and trailing whitespace -->
<input v-model.trim="username" />
<!-- modifiers can be combined -->
<input v-model.trim.lazy="comment" />
</template>.lazy is useful for expensive reactions such as filtering a large list. .number matters because the DOM always returns strings, even from type="number". Note that .number is applied automatically when the input has type="number", so you only need it explicitly on text inputs.
.number value with === before the user has typed anything: an empty input yields '', not 0.@submit without .prevent, which reloads the page and loses the state.v-model on a native element to emit a custom event; the component form of v-model (modelValue and update:modelValue) is a different mechanism covered in the component events lesson.Three checkboxes share `v-model="colors"`. What must `colors` be initialized as?
v-model binds the correct property and event for each control type; initialize state instead of using HTML value/selected attributes.<select multiple> map to arrays, radios map to the selected value.true-value, false-value and :value let controls store non-string values, including objects..lazy syncs on change, .number casts to a number, .trim strips whitespace; they can be combined.@submit.prevent on forms handled by Vue.Next lesson: Computed Properties and Watchers — derive values from state and react to changes with side effects.