Vue.js Component v-model

The v-model directive links an input to a data property so both stay in step. Custom components can offer the same two-way link. A parent writes v-model="rating" on a component tag, and the component reads and updates that value without extra wiring in the parent. Rating stars, toggle switches, date pickers, and search boxes all use this pattern.

Think of a thermostat on the wall. You read the current temperature from the display and turn a dial to change it. The display and the dial belong to one device, and the room setting stays in sync. A component with v-model behaves the same way: the parent owns the value, and the component displays it and requests changes.

What v-model Does Behind the Scenes

Vue expands v-model on a component into one prop and one event. The prop is named modelValue. The event is named update:modelValue. The component receives the prop for display and emits the event when the user changes something.

Diagram: Expanded v-model

Parent writes:
  <StarRating v-model="score" />

Vue turns it into:
  <StarRating
     :modelValue="score"
     @update:modelValue="score = $event" />

Parent score  --(prop)-->   Component shows the stars
Parent score  <--(event)--  Component reports a new choice

Build a Component with Props and Emits

The component below shows five stars. A click on a star emits the new number to the parent.

<div id="app">
  <star-rating v-model="score"></star-rating>
  <p>Your score: {{ score }}</p>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return { score: 3 };
    }
  });

  app.component("star-rating", {
    props: ["modelValue"],
    emits: ["update:modelValue"],
    template: `
      <span>
        <button v-for="n in 5" :key="n"
                @click="$emit('update:modelValue', n)">
          {{ n <= modelValue ? "★" : "☆" }}
        </button>
      </span>`
  });

  app.mount("#app");
</script>

A click on the fourth star emits update:modelValue with the number 4. The parent stores 4 in score, passes it back down, and the component redraws four filled stars.

The defineModel Shortcut

Recent Vue 3 versions include defineModel() for components written with <script setup>. The macro creates the prop and the event together and returns a ref you can read and write like normal data.

<!-- ToggleSwitch.vue -->
<script setup>
const on = defineModel();
</script>

<template>
  <button @click="on = !on">
    {{ on ? "ON" : "OFF" }}
  </button>
</template>

<!-- Parent.vue -->
<ToggleSwitch v-model="darkMode" />

Assigning to on sends the new value to the parent. The macro removes the repeated prop and emit code.

Multiple v-model Bindings

One component can accept several linked values. Add an argument after v-model to name each one.

<UserName v-model:first="firstName" v-model:last="lastName" />

<!-- UserName.vue -->
<script setup>
const first = defineModel("first");
const last = defineModel("last");
</script>

<template>
  <input v-model="first" placeholder="First name">
  <input v-model="last" placeholder="Last name">
</template>

Diagram: Two Named Bindings

Parent                        UserName component
firstName  <-->  v-model:first  <-->  first input box
lastName   <-->  v-model:last   <-->  last input box

Format Input with Modifiers

Built-in modifiers such as .trim and .number work on component bindings. Custom modifiers arrive in the component through a second value, which lets a component clean the text before it reaches the parent.

const [text, modifiers] = defineModel({
  set(value) {
    return modifiers.capitalize
      ? value.charAt(0).toUpperCase() + value.slice(1)
      : value;
  }
});

A parent writes v-model.capitalize="title", and every update arrives with a capital first letter.

Common Mistakes

  • Changing a modelValue prop directly inside the child. Props are read-only, so emit an event instead.
  • Forgetting the emits declaration in Options API components.
  • Using a mismatched event name. The name must be exactly update:modelValue for the default binding.

Summary

Component v-model expands into the modelValue prop and the update:modelValue event. The parent owns the data, and the child displays it and requests changes. The defineModel() macro shortens the setup, named arguments allow several bindings on one component, and modifiers let a component adjust values before they travel upward.