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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs