Vue cookbook · Beginner

Build custom inputs with defineModel and modifiers

A custom input component needs v-model support with trimming or number conversion.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Use defineModel for the value; read modifiers (like v-model.trim) from the second tuple element and apply them in the model's set transformer.

Solution

<!-- TextField.vue -->
<script setup lang="ts">
const [model, modifiers] = defineModel<string, "trim" | "lowercase">({
  default: "",
  set(value) {
    let next = value;
    if (modifiers.trim) next = next.trim();
    if (modifiers.lowercase) next = next.toLowerCase();
    return next;
  },
});
defineProps<{ label: string; error?: string }>();
const id = useId();
</script>

<template>
  <label :for="id">{{ label }}</label>
  <input :id="id" v-model="model" :aria-invalid="!!error" :aria-describedby="error ? `${id}-error` : undefined" />
  <p v-if="error" :id="`${id}-error`" role="alert">{{ error }}</p>
</template>
<!-- Parent -->
<script setup lang="ts">
import { ref } from "vue";
import TextField from "./TextField.vue";

const email = ref("");
</script>

<template>
  <TextField v-model.trim.lowercase="email" label="Email" />
  <p>Saved as: {{ email }}</p>
</template>

Pitfalls

  • Mutating the prop directly instead of using defineModel.
  • Generating random IDs for labels that differ between server and client; useId() (Vue 3.5) is SSR-safe.
  • Forgetting accessible labels and error associations on custom inputs.

Variations

  • Several models on one component: defineModel("first") and defineModel("last") for v-model:first.
  • Use the get transformer to format the displayed value (for example currency).

Further reading: v-model modifiers

Reference: https://vuejs.org/guide/components/v-model.html#handling-v-model-modifiers

More Vue recipes

Was this page helpful?