When you write v-model on a native <input>, Vue binds its value and listens for input events. When you write v-model on a custom component, Vue does something similar: it passes a modelValue prop and listens for an update:modelValue event. The component controls what the user sees and when to emit updates.
What v-model expands to
<!-- These two are equivalent -->
<SearchInput v-model="query" />
<SearchInput :modelValue="query" @update:modelValue="query = $event" />The parent provides the data through a prop. The child emits an event when the data should change. The parent decides whether to accept the change. This preserves one-way data flow.
defineModel (Vue 3.4+)
defineModel is the modern way to implement v-model in a child component. It creates a ref that you can read and write. Vue handles the prop binding and event emission behind the scenes:
<!-- SearchInput.vue -->
<script setup lang="ts">
const model = defineModel<string>()
</script>
<template>
<input
:value="model"
@input="model = ($event.target as HTMLInputElement).value"
/>
</template>Or bind it directly with v-model on a native input:
<template>
<input v-model="model" />
</template>Before Vue 3.4, you had to declare the prop and emit separately. defineModel removes that boilerplate.
Named v-models (multiple bindings)
A single component can have multiple v-model bindings by giving each a name:
<!-- Parent -->
<UserForm v-model:firstName="first" v-model:lastName="last" /><!-- UserForm.vue -->
<script setup lang="ts">
const firstName = defineModel<string>('firstName')
const lastName = defineModel<string>('lastName')
</script>
<template>
<input v-model="firstName" placeholder="First name" />
<input v-model="lastName" placeholder="Last name" />
</template>Each named v-model becomes its own prop/emit pair: :firstName + @update:firstName.
v-model modifiers
Vue has built-in modifiers (.trim, .number, .lazy), and you can define custom ones. The parent passes modifiers, and the child accesses them through defineModel:
<!-- Parent -->
<SearchInput v-model.capitalize="query" /><!-- SearchInput.vue -->
<script setup lang="ts">
const [model, modifiers] = defineModel<string>({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<template>
<input v-model="model" />
</template>See also: How do multiple v-model bindings work? · What are custom v-model modifiers? · Why doesn't mutating an object through defineModel update the parent?
References
- Component v-model - Vue.js docs
- defineModel() - Vue.js docs
- v-model modifiers - Vue.js docs