Skip to content
← All questions
⚡ Intermediate

How does v-model work on custom components?

🧩 Components📌 Directives🔄 v-model

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.

Data flow diagram for v-model with custom components using defineModel

What v-model expands to

vue
<!-- These two are equivalent -->
<SearchInput v-model="query" />
<SearchInput :modelValue="query" @update:modelValue="query = $event" />
Open in Vue Playground

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:

vue
<!-- SearchInput.vue -->
<script setup lang="ts">
const model = defineModel<string>()
</script>

<template>
  <input
    :value="model"
    @input="model = ($event.target as HTMLInputElement).value"
  />
</template>
Open in Vue Playground

Or bind it directly with v-model on a native input:

vue
<template>
  <input v-model="model" />
</template>
Open in Vue Playground

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:

vue
<!-- Parent -->
<UserForm v-model:firstName="first" v-model:lastName="last" />
Open in Vue Playground
vue
<!-- 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>
Open in Vue Playground

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:

vue
<!-- Parent -->
<SearchInput v-model.capitalize="query" />
Open in Vue Playground
vue
<!-- 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>
Open in Vue Playground

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

Released under the MIT License.