JAVASCRIPT
Customizing v-model for Reusable Input Components in Vue 3
Learn to build flexible and reusable custom form inputs in Vue 3 by leveraging the `modelValue` prop and `update:modelValue` event to customize `v-model` behavior for enhanced component design.
// 1. Create a custom input component (e.g., CustomTextInput.vue)
<template>
<div class="custom-text-input">
<label v-if="label">{{ label }}:</label>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
v-bind="$attrs"
/>
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
</div>
</template>
<script setup>
import { defineProps, defineEmits, useAttrs } from 'vue';
// Define props, explicitly including modelValue
const props = defineProps({
modelValue: { // The default prop for v-model
type: [String, Number],
default: ''
},
label: {
type: String,
default: ''
},
errorMessage: {
type: String,
default: ''
}
});
// Define emits, explicitly including update:modelValue
const emit = defineEmits(['update:modelValue']);
// Inherit and pass down attributes like placeholder, id etc.
// (useful if you don't want to define every HTML attribute as a prop)
const $attrs = useAttrs();
</script>
<style scoped>
.custom-text-input {
margin-bottom: 15px;
}
.custom-text-input label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.custom-text-input input {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
box-sizing: border-box;
}
.custom-text-input input:focus {
outline: none;
border-color: #007bff;
}
.error-message {
color: red;
font-size: 0.85em;
margin-top: 5px;
}
</style>
// 2. Use the custom input component with v-model (e.g., in App.vue)
<template>
<div>
<h1>User Form</h1>
<CustomTextInput
v-model="userName"
label="Your Name"
placeholder="Enter your name"
:error-message="nameError"
id="userNameInput"
/>
<CustomTextInput
v-model.number="userAge"
label="Your Age"
type="number"
placeholder="Enter your age"
:error-message="ageError"
id="userAgeInput"
/>
<p>Name: {{ userName }}</p>
<p>Age: {{ userAge }} (Type: {{ typeof userAge }})</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import CustomTextInput from './components/CustomTextInput.vue'; // Adjust path as needed
const userName = ref('');
const userAge = ref(null);
const nameError = computed(() =>
userName.value.length < 3 && userName.value !== '' ? 'Name must be at least 3 characters.' : ''
);
const ageError = computed(() =>
userAge.value !== null && (userAge.value < 18 || userAge.value > 100) ? 'Age must be between 18 and 100.' : ''
);
</script>
How it works: This snippet demonstrates how to customize `v-model` for reusable form input components in Vue 3. By default, `v-model` on a component expects a `modelValue` prop and an `update:modelValue` event. The `CustomTextInput` component defines a `modelValue` prop to receive its value and emits an `update:modelValue` event whenever its internal input changes. The parent component then uses `v-model` on `CustomTextInput` just like a native input, allowing for clean two-way data binding. It also shows how to pass additional attributes using `$attrs` and handle modifiers like `.number` implicitly.