Vue has three built-in modifiers for v-model on native inputs (.lazy, .number, .trim). On custom components, you can define your own modifiers that transform the value as it flows in or out.
Built-in modifiers recap
<template>
<input v-model.lazy="msg" /> <!-- sync on change, not input -->
<input v-model.number="age" /> <!-- cast to number via parseFloat -->
<input v-model.trim="name" /> <!-- trim whitespace -->
</template>The .number modifier has a gotcha: it returns an empty string (not 0 or NaN) when the input is cleared, and parseFloat("123abc") returns 123, not NaN.
Custom modifiers with defineModel (Vue 3.4+)
defineModel returns a tuple [ref, modifiers] when you provide a set transform:
<!-- CurrencyInput.vue -->
<script setup lang="ts">
const [model, modifiers] = defineModel<number>({
set(value) {
if (modifiers.round) {
return Math.round(value)
}
return value
}
})
</script>
<template>
<input type="number" v-model="model" />
</template><!-- Parent.vue -->
<template>
<CurrencyInput v-model.round="price" />
</template>When the user types 9.7, the parent receives 10.
Multiple modifiers
Modifiers are just boolean flags on an object. You can combine them:
<script setup lang="ts">
const [model, modifiers] = defineModel<string>({
set(value) {
let result = value
if (modifiers.capitalize) {
result = result.charAt(0).toUpperCase() + result.slice(1)
}
if (modifiers.trim) {
result = result.trim()
}
return result
}
})
</script><!-- Parent -->
<TextInput v-model.capitalize.trim="name" />get transform
You can also transform the value coming from the parent:
<script setup lang="ts">
const [model, modifiers] = defineModel<string>({
get(value) {
if (modifiers.uppercase) {
return value.toUpperCase()
}
return value
},
set(value) {
return value.toLowerCase()
}
})
</script>Named models with modifiers
Custom modifiers work with named v-model too:
<script setup lang="ts">
const [firstName, firstModifiers] = defineModel<string>('firstName')
const [lastName, lastModifiers] = defineModel<string>('lastName')
</script><UserForm v-model:first-name.capitalize="first" v-model:last-name.capitalize="last" />Before defineModel (Vue < 3.4)
Modifiers arrive as a prop named modelModifiers (or nameModifiers for named models):
<script setup lang="ts">
const props = defineProps<{
modelValue: string
modelModifiers?: { capitalize?: boolean }
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
function handleInput(e: Event) {
let value = (e.target as HTMLInputElement).value
if (props.modelModifiers?.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input :value="modelValue" @input="handleInput" />
</template>defineModel eliminates all this boilerplate.