defineModel — One Line Makes a Component v-model-able
Custom v-model on Components
defineModel returns a writable ref that automatically syncs with the parent's v-model. It replaces the older props plus update emit pattern.
What you'll learn
- Call defineModel inside script setup
- Use the model ref in the template
- Type the model with a generic
Custom components can opt into v-model with a single call to defineModel. It is reactive both ways: when the parent’s value changes, your ref updates; when you write to the ref, the parent receives the new value.
The Basic Pattern
<!-- MyInput.vue -->
<script setup lang="ts">
const model = defineModel<string>()
</script>
<template>
<input
:value="model"
@input="model = ($event.target as HTMLInputElement).value"
/>
</template> The parent uses it like a native input:
<script setup lang="ts">
import { ref } from 'vue'
import MyInput from './MyInput.vue'
const name = ref('')
</script>
<template>
<MyInput v-model="name" />
<p>Hello {{ name }}</p>
</template> Defaults and Validation
defineModel accepts the same options as defineProps:
const model = defineModel<string>({
required: true,
default: '',
validator: (v: string) => v.length <= 100,
}) Reading Modifiers
When the parent applies a modifier (<MyInput v-model.uppercase="x">), you receive them as a second value from defineModel. You can transform the value as it flows through.
<script setup lang="ts">
const [model, modifiers] = defineModel<string>()
function onInput(e: Event) {
let v = (e.target as HTMLInputElement).value
if (modifiers.uppercase) v = v.toUpperCase()
model.value = v
}
</script>
<template>
<input :value="model" @input="onInput" />
</template> Under the hood defineModel still declares a prop and an emit — it just hides the boilerplate.
Multiple v-model →