v-model on components (defineModel)

Vue · Practical · 10 min

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:modelValue

The 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-model on a component = modelValue prop + update:modelValue event
  • defineModel() 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.

Check yourself

  1. Inside a child, `const m = defineModel()` and the code runs `m.value = 5`. What happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: vuejs.org/guide/components/v-model.html