Custom v-model on Components

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.

4 min read Level 2/5 #vue#forms#v-model
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 →