Form Input Bindings
|
This section documents the current Vue 3.x release line as published at
the official Vue.js documentation, which is the reference these pages are written and
verified against. No specific patch version is pinned. The Composition API with This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since Vue and its ecosystem iterate quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
v-model creates a two-way binding between a form control and a piece of reactive state: the control shows
the state, and editing the control writes it back. It picks the right DOM property and event for each control
type. The reference is Form Input Bindings.
The basics
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
<template>
<input v-model="message" placeholder="type here">
<p>{{ message }}</p>
</template>
v-model ignores the initial value, checked, or selected attribute on the element — the reactive
state is the single source of truth, so set the initial value on the ref instead. See
Basic Usage.
What v-model desugars to
For a text input, v-model="message" expands to roughly a :value bind plus an @input listener:
<input
:value="message"
@input="message = $event.target.value">
Different controls use different pairs — this is why the same directive covers all of them:
| Control | Bound property | Listened event |
|---|---|---|
text, |
|
|
checkbox, radio |
|
|
|
|
|
Knowing the expansion matters when you need something in between: bind :value yourself and add an @input
that transforms the text before storing it.
Text and textarea
<template>
<input v-model="title">
<textarea v-model="bio"></textarea>
</template>
Text interpolation does not work inside <textarea> — use v-model.
Checkbox
See Checkbox. A lone checkbox binds to a boolean:
<script setup>
import { ref } from 'vue'
const agreed = ref(false)
</script>
<template>
<input type="checkbox" v-model="agreed">
</template>
Several checkboxes that share one array binding collect the value of each checked box:
<script setup>
import { ref } from 'vue'
const picked = ref([])
</script>
<template>
<input type="checkbox" value="vue" v-model="picked">
<input type="checkbox" value="svelte" v-model="picked">
<input type="checkbox" value="solid" v-model="picked">
<!-- picked.value is e.g. ['vue', 'solid'] -->
</template>
true-value and false-value make a single checkbox store something other than the booleans — and they
only affect v-model, not the element’s value attribute. Bind them with :true-value / :false-value for
non-string values:
<template>
<input type="checkbox" v-model="answer" true-value="yes" false-value="no">
<input type="checkbox" v-model="answer" :true-value="dynamicYes" :false-value="dynamicNo">
</template>
Radio
See Radio.
<script setup>
import { ref } from 'vue'
const size = ref('m')
</script>
<template>
<input type="radio" value="s" v-model="size"> Small
<input type="radio" value="m" v-model="size"> Medium
<!-- :value for a non-string or dynamic value -->
<input type="radio" :value="{ id: 3 }" v-model="size"> Custom
</template>
Select
See Select.
<template>
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
</select>
</template>
Provide an empty disabled option when the initial value matches no <option> — otherwise iOS renders the
select in an unselectable state. For a multiple select, bind to an array:
<template>
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</template>
Options rendered with v-for use :value, which can hold any type, not just a string — see
Value Bindings:
<template>
<select v-model="selected">
<option v-for="o in options" :key="o.id" :value="o">{{ o.text }}</option>
</select>
</template>
Modifiers
See v-model modifiers. They chain, e.g.
v-model.trim.lazy.
<template>
<!-- sync on "change" instead of every "input" -->
<input v-model.lazy="msg">
<!-- cast to a number with parseFloat; kept as-is if the result is NaN -->
<input v-model.number="age">
<!-- strip leading and trailing whitespace -->
<input v-model.trim="name">
</template>
<input type="number"> already coerces to a number, so .number is mainly for text inputs that should hold
numeric values.
Validation is not built in
Vue’s core has no form-validation layer. For real forms, reach for a dedicated library: VeeValidate wires field-level and whole-form schema validation (Zod, Yup, Valibot) into Vue’s reactivity. The official guide covers only the binding mechanics on this page.
v-model on components
Everything above is v-model on native elements. v-model also works on your own components, backed by
defineModel(), with support for arguments and custom modifiers — see
Component Events and v-model.
See also
-
Event Handling — the
@input/@changelistenersv-modelis built on. -
Watchers — reacting when a bound form value changes.
-
Computed Properties — a writable
computedas a filtered view over a form model. -
TypeScript Reference — typing the objects a form binds to.