JAVASCRIPT
Building Custom `v-model` Components in Vue 3 with `modelValue` and `update:modelValue`
Extend Vue 3's `v-model` functionality to your custom components using `modelValue` prop and `update:modelValue` event for two-way data binding.
// CustomInput.vue
<template>
<div class="custom-input-wrapper">
<label for="custom-field">{{ label }}:</label>
<input
type="text"
:id="id"
:value="modelValue"
@input="handleInput"
class="custom-input"
:placeholder="placeholder"
/>
</div>
</template>
<script setup>
import { defineProps, defineEmits, computed } from 'vue';
const props = defineProps({
modelValue: String, // The prop name MUST be 'modelValue'
label: {
type: String,
default: 'Field',
},
placeholder: {
type: String,
default: '',
},
id: {
type: String,
default: () => `custom-input-${Math.random().toString(36).substr(2, 9)}`,
},
});
// The event name MUST be 'update:modelValue'
const emit = defineEmits(['update:modelValue']);
function handleInput(event) {
emit('update:modelValue', event.target.value);
}
</script>
<style scoped>
.custom-input-wrapper {
margin-bottom: 15px;
}
.custom-input {
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
max-width: 300px;
box-sizing: border-box;
}
.custom-input:focus {
outline: none;
border-color: #42b983;
box-shadow: 0 0 0 3px rgba(66, 185, 131, 0.2);
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
</style>
// App.vue (Parent component usage)
<template>
<div>
<h1>Custom v-model Example</h1>
<CustomInput
v-model="userName"
label="Your Name"
placeholder="Enter your name"
/>
<p>User Name: <strong>{{ userName }}</strong></p>
<CustomInput
v-model="userEmail"
label="Your Email"
id="email-field"
/>
<p>User Email: <strong>{{ userEmail }}</strong></p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
const userName = ref('John Doe');
const userEmail = ref('');
</script>
How it works: Vue 3 allows you to implement `v-model` on custom components, making them behave like native form inputs with two-way data binding. The convention requires a `modelValue` prop to receive the value from the parent and an `update:modelValue` event to emit changes back. In `CustomInput.vue`, the component accepts `modelValue` as a prop and emits `update:modelValue` whenever its internal input changes. This setup enables the parent component (`App.vue`) to use `v-model="userName"` directly, creating a clean and familiar syntax for complex custom form controls, improving reusability and developer experience.