← Back to all snippets
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.

Need help integrating this into your project?

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

Hire DigitalCodeLabs