JAVASCRIPT
Custom Input Component with `v-model` and Validation Feedback
Build a reusable custom input component in Vue 3 that correctly integrates with `v-model` and provides immediate validation feedback without complex regex.
// components/BaseInput.vue
<template>
<div class="base-input">
<label v-if="label">{{ label }}</label>
<input
:type="type"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
:placeholder="placeholder"
:class="{ 'is-invalid': hasError }"
/>
<p v-if="hasError" class="error-message">{{ errorMessage }}</p>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
defineProps({
modelValue: { type: [String, Number], default: '' },
label: { type: String, default: '' },
type: { type: String, default: 'text' },
placeholder: { type: String, default: '' },
hasError: { type: Boolean, default: false },
errorMessage: { type: String, default: 'Input is invalid.' },
});
defineEmits(['update:modelValue']);
</script>
<style scoped>
.base-input {
margin-bottom: 15px;
}
.base-input label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.base-input input {
width: 100%;
padding: 8px 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.base-input input.is-invalid {
border-color: red;
}
.error-message {
color: red;
font-size: 0.9em;
margin-top: 5px;
}
</style>
// views/ContactForm.vue
<template>
<form @submit.prevent="submitForm">
<BaseInput
label="Your Name"
type="text"
placeholder="Enter your name"
v-model="name"
:has-error="name.length < 3 && nameTouched"
:error-message="'Name must be at least 3 characters.'"
@blur="nameTouched = true"
/>
<BaseInput
label="Your Email"
type="email"
placeholder="Enter your email"
v-model="email"
:has-error="!isValidEmail(email) && emailTouched"
:error-message="'Please enter a valid email.'"
@blur="emailTouched = true"
/>
<button type="submit" :disabled="!isFormValid">Submit</button>
</form>
</template>
<script setup>
import { ref, computed } from 'vue';
import BaseInput from '@/components/BaseInput.vue';
const name = ref('');
const email = ref('');
const nameTouched = ref(false);
const emailTouched = ref(false);
const isValidEmail = (value) => {
// A simple email validation for demonstration, not robust regex
return value.includes('@') && value.includes('.');
};
const isFormValid = computed(() => {
return name.value.length >= 3 && isValidEmail(email.value);
});
const submitForm = () => {
if (isFormValid.value) {
alert(`Form Submitted! Name: ${name.value}, Email: ${email.value}`);
// Reset form or send data to backend
name.value = '';
email.value = '';
nameTouched.value = false;
emailTouched.value = false;
} else {
alert('Please correct the form errors.');
nameTouched.value = true;
emailTouched.value = true;
}
};
</script>
How it works: This example illustrates how to create a highly reusable `BaseInput` component that fully supports `v-model` in Vue 3. By defining a `modelValue` prop and emitting an `update:modelValue` event, the custom input behaves just like native inputs when using `v-model`. It also incorporates props for `label`, `type`, `placeholder`, and crucially, `hasError` and `errorMessage` for providing visual validation feedback directly within the component, allowing parent components to easily control its validation state.