Reusable inputs, toggles and pickers should feel like native inputs to the parent.
On a component, v-model="query" expands to a prop plus an event: :modelValue="query" and @update:modelValue="v => query = v". Since Vue 3.4 the `defineModel()` macro implements both sides for you:
const model = defineModel() // a ref synced with the parent
model.value = 'new' // emits update:modelValueThe returned ref reads the parent's prop and, when you assign to it, emits the update event. You can bind it straight to a native input: <input v-model="model">.
Options mirror props: defineModel({ type: Number, default: 0, required: true }), or typed with defineModel<number>().
Components can expose several models by name. The parent writes v-model:title="t" and v-model:done="d", and the child declares defineModel('title') and defineModel('done').
Custom modifiers are supported: the parent writes v-model.capitalize="text" and the child destructures const [model, modifiers] = defineModel({ set(v) { return modifiers.capitalize ? cap(v) : v } }).
One subtlety: if the parent does not pass a value, defineModel keeps a local value so the component still works uncontrolled. If you give a default and the parent passes nothing, the parent's ref and the child can get out of sync, so prefer requiring the model or matching defaults on both sides.
Example
<!-- StarRating.vue -->
<script setup>
const rating = defineModel({ type: Number, default: 0 })
const note = defineModel('note', { type: String })
</script>
<template>
<span v-for="n in 5" :key="n" @click="rating = n">
{{ n <= rating ? '★' : '☆' }}
</span>
<input v-model="note" placeholder="Why?">
</template>
<!-- Review.vue -->
<script setup>
import { ref } from 'vue'
import StarRating from './StarRating.vue'
const stars = ref(3)
const why = ref('')
</script>
<template>
<StarRating v-model="stars" v-model:note="why" />
<p>{{ stars }} stars: {{ why }}</p>
</template>Remember
v-modelon a component =modelValueprop +update:modelValueeventdefineModel()returns a ref that emits updates when assigned- Use named models (
v-model:title) for several bindings on one component
Watch out for
- Mutating the parent's object via
model.value.field = x; that bypasses the update event. Assign a new object instead.