Previously, for a component to support two-way binding with v-model, it needs to (1) declare a prop and (2) emit a corresponding update:propName event when it intends to update the prop:

<!-- BEFORE -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
console.log(props.modelValue)

function onInput(e) {
  emit('update:modelValue', e.target.value)
}
</script>

<template>
  <input :value="modelValue" @input="onInput" />
</template>

3.3 simplifies the usage with the new defineModel macro. The macro automatically registers a prop, and returns a ref that can be directly mutated:

<!-- AFTER -->
<script setup>
const modelValue = defineModel()
console.log(modelValue.value)
</script>

<template>
  <input v-model="modelValue" />
</template>

This feature is experimental and requires explicit opt-in.

When you use Vite, you can enable it like this:

// vite.config.js
export default {
  plugins: [
    vue({
      script: {
        defineModel: true
      }
    })
  ]
}