Understanding the “v-model” Magic
At its core, v-model is a directive that provides two-way data binding on form inputs, textareas, and components. It simplifies the process of synchronizing a parent component’s data with a child component’s state, eliminating the need for manual event handling for common use cases.
How v-model Works in Vue 3
In Vue 3, v-model on a component is essentially syntactic sugar for passing a modelValue prop and listening for an update:modelValue event.
- Prop: The component receives a prop named
modelValue. - Event: When the component wants to update the value, it emits an event named
update:modelValuewith the new value.
For example, this code:
<!-- ParentComponent.vue -->
<CustomInput v-model="searchText" />
Is roughly equivalent to this:
<!-- Is roughly equivalent to: -->
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
Understanding this prop/event pattern is the key to creating your own custom components with v-model support.
Creating a Custom v-model
Let’s build a simple CustomInput.vue component that fully supports v-model. This component will wrap a standard HTML input element.
Step 1: Define Props and Emits
First, we need to declare that our component accepts a modelValue prop and can emit an update:modelValue event. We do this in the <script setup> block.
<!-- CustomInput.vue -->
<script setup>
defineProps({
modelValue: {
type: String,
default: ''
}
});
defineEmits(['update:modelValue']);
</script>
Step 2: Bind the Prop and Emit the Update
Next, in the component’s template, we bind the native input’s value to our modelValue prop. Then, we listen for the native input event and emit our update:modelValue event with the input’s new value.
<!-- CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
class="custom-input"
placeholder="Enter text here..."
/>
</template>
<script setup>
defineProps({
modelValue: {
type: String,
default: ''
}
});
defineEmits(['update:modelValue']);
</script>
<style scoped>
.custom-input {
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
}
</style>
Step 3: Using the Component
That’s it! Now you can use CustomInput in a parent component with v-model, and the data will be perfectly synchronized.
<!-- Parent.vue -->
<template>
<div>
<h3>My Form</h3>
<CustomInput v-model="message" />
<p>Current Message: {{ message }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
const message = ref('');
</script>
Customizing v-model Arguments
What if your component needs to two-way bind multiple values? Vue 3 allows you to pass an argument to v-model, creating a named model. For example, v-model:title.
This will pass a prop named title and expect an event named update:title.
Here’s how you’d use it:
<!-- Parent using a component with multiple v-models -->
<ArticleEditor
v-model:title="articleTitle"
v-model:content="articleContent"
/>
And here is how you would implement the ArticleEditor component:
<!-- ArticleEditor.vue -->
<script setup>
defineProps({
title: { type: String, default: '' },
content: { type: String, default: '' }
});
defineEmits(['update:title', 'update:content']);
</script>
<template>
<div class="editor">
<input
:value="title"
@input="$emit('update:title', $event.target.value)"
placeholder="Article Title"
/>
<textarea
:value="content"
@input="$emit('update:content', $event.target.value)"
placeholder="Article content..."
></textarea>
</div>
</template>
Key Takeaways
- Default Model:
v-modeldefaults to amodelValueprop and anupdate:modelValueevent. - Custom Model: You can create named models with
v-model:argumentName, which corresponds to anargumentNameprop and anupdate:argumentNameevent. - Clarity: Use
definePropsanddefineEmitsin the Composition API to clearly state your component’sv-modelinterface. - Simplicity: Creating custom
v-modelcomponents is a powerful pattern for building reusable and intuitive form elements in Vue.



