
Custom Events and Component Communication in Vue – `emit`, `provide/inject`, and When to Use a Store
} -->
v-model
is one of the most powerful features in Vue, enabling two-way data binding between a parent and a child component. But when you create custom components, you need to wire it up manually — and there are a few ways to do it right.
Let’s break it down with examples, including the new defineModel()
helper introduced in Vue 3.3+.
modelValue
+ emit
When you use v-model
on a custom component, Vue internally binds the modelValue
prop and listens for an update:modelValue
event.
<BaseInput />
(Traditional Way)<!-- BaseInput.vue -->
<template>
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>
<script setup lang="ts">
defineProps<{ modelValue: string }>()
defineEmits(['update:modelValue'])
</script>
Now, you can use it like this in a parent:
<BaseInput v-model="name" />
Vue handles the rest:
Even if you destructure modelValue with defineProps()
, emitting the right event is on you. Misspelling ‘update:modelValue
’ or not emitting it at all will break the binding.
defineModel()
Vue 3.3+ introduces defineModel()
— a powerful helper that simplifies two-way binding.
<BaseInput />
(With defineModel())<template>
<input v-model="value" />
</template>
<script setup lang="ts">
const value = defineModel<string>()
</script>
That’s it — defineModel():
update:modelValue
event when you change the bound variableIn parent:
<BaseInput v-model="name" />
Want to use a custom prop like v-model:title
?
<script setup lang="ts">
const title = defineModel<string>('title')
</script>
Then use it like this:
<MyComponent v-model:title="pageTitle" />
Vue will:
update:title
eventv-model
might look like magic, but under the hood, it’s just smart prop/event wiring. Use defineModel()
in new code to simplify your components and reduce boilerplate.
If you’re maintaining older projects, stick with modelValue + emit, but now you know the cleaner path moving forward.
Bartłomiej Nowak
Programmer
Programmer focused on performance, simplicity, and good architecture. I enjoy working with modern JavaScript, TypeScript, and backend logic — building tools that scale and make sense.