JAVASCRIPT
Implementing `v-model` on Custom Vue 3 Components
Learn to make your custom Vue 3 components compatible with `v-model`, enabling two-way data binding for cleaner and more intuitive form input abstraction.
<!-- App.vue -->
<template>
<div>
<p>Parent Value: {{ myValue }}</p>
<CustomInput v-model="myValue" placeholder="Enter text" />
<CustomInput v-model:checked="isChecked" type="checkbox" label="Check me" />
<p>Is Checked: {{ isChecked }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import CustomInput from './components/CustomInput.vue';
const myValue = ref('Hello Vue!');
const isChecked = ref(false);
</script>
<!-- components/CustomInput.vue -->
<template>
<label v-if="type === 'checkbox'">
<input
:type="type"
:checked="checked"
@change="$emit('update:checked', $event.target.checked)"
/>
{{ label }}
</label>
<input
v-else
:type="type"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
:placeholder="placeholder"
/>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
modelValue: { // Default prop name for v-model
type: String,
default: ''
},
checked: { // Prop name for v-model:checked
type: Boolean,
default: false
},
type: {
type: String,
default: 'text'
},
placeholder: {
type: String,
default: ''
},
label: {
type: String,
default: ''
}
});
const emit = defineEmits(['update:modelValue', 'update:checked']); // Default event name for v-model
</script>
<style scoped>
input {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
}
label {
display: block;
margin-bottom: 10px;
}
</style>
How it works: This snippet demonstrates how to implement `v-model` on custom Vue 3 components, allowing for two-way data binding. For a standard input, the component accepts a `modelValue` prop and emits an `update:modelValue` event. For multiple `v-model` bindings (e.g., `v-model:checked`), custom prop and event names are used (e.g., `checked` prop and `update:checked` event). This pattern enables you to encapsulate complex input logic within reusable components while maintaining a clean and familiar API for parent components.