# v-model vs modelValue in Vue 3: Understanding the Directive and Prop Relationship

> Understand the difference between v-model and modelValue in Vue 3. Learn how v-model uses the modelValue prop and update event for seamless two-way data binding in your components.

- Repository: [Vue/core](https://github.com/vuejs/core)
- Tags: deep-dive
- Published: 2026-02-13

---

**The `v-model` directive is syntactic sugar that automatically wires a two-way binding by passing data to a `modelValue` prop and listening for an `update:modelValue` event, while `modelValue` itself is merely a standard component prop that receives that data.**

In Vue 3, the distinction between the template-level directive and the component-level prop is fundamental to how two-way binding operates. While the `v-model` directive handles the coordination logic in the runtime, the `modelValue` prop serves as the actual data container defined on the child component.

## What is the v-model Directive?

The `v-model` directive is a template-level abstraction that lives in Vue’s runtime compilation system. When the compiler encounters `v-model="foo"` on a component, it rewrites the expression to bind the value as a prop and attach an event listener that updates the parent’s state.

According to the Vue 3 source code, the directive’s implementation resides in [`packages/runtime-core/src/directives/vModel.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/directives/vModel.ts). This file exports the directive definition containing `beforeMount`, `mounted`, `updated`, and `unmounted` hooks that manage the synchronization between the DOM element (or child component) and the reactive data. The directive handles the automatic propagation of value changes without requiring manual event wiring in the template.

## What is the modelValue Prop?

`modelValue` is a standard Vue component prop—no different from `title` or `disabled`—that serves as the default target for `v-model` bindings. In [`packages/runtime-core/src/component.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/component.ts), the core logic processes component props, including `modelValue`, as part of the normal options API or setup function handling.

When you accept `v-model` in a child component, you explicitly declare `modelValue` (or a custom name) in your props definition and emit the corresponding `update:modelValue` event to signal changes. The prop itself holds no special internal behavior; it is simply the conventional name that the `v-model` directive expects when no custom argument is provided.

## How v-model and modelValue Work Together

During template compilation, Vue transforms `v-model` syntax into explicit prop binding and event listening. This transformation bridges the directive layer and the component API layer.

For a default binding:

```html
<MyInput v-model="text" />

```

Vue compiles this to the equivalent of:

```html
<MyInput :modelValue="text" @update:modelValue="value => text = value" />

```

When using a custom prop name via the argument syntax:

```html
<MyInput v-model:checked="isChecked" />

```

This compiles to:

```html
<MyInput :checked="isChecked" @update:checked="value => isChecked = value" />

```

Thus, **the directive (`v-model`) belongs to the template compilation/runtime layer**, automatically generating the boilerplate to keep parent and child state synchronized, while **the prop (`modelValue` or `checked`) belongs to the component API layer**, acting as the passive receiver of that data.

## Implementation Examples

The following patterns demonstrate the separation of concerns in practice.

**Parent Component (Template):**

```html
<!-- default modelValue binding -->
<CustomInput v-model="username" />

<!-- custom prop name binding -->
<CustomCheckbox v-model:checked="agreed" />

```

**Child Component (Options API) in [`CustomInput.vue`](https://github.com/vuejs/core/blob/main/CustomInput.vue):**

```javascript
export default {
  props: {
    // Default prop for v-model
    modelValue: String,
    // Custom prop when using v-model:checked
    checked: Boolean
  },
  emits: ['update:modelValue', 'update:checked'],
  methods: {
    onInput(e) {
      // Emit for default v-model
      this.$emit('update:modelValue', e.target.value)
    },
    onToggle(newState) {
      // Emit for custom v-model
      this.$emit('update:checked', newState)
    }
  }
}

```

**Child Component (Composition API) in [`CustomInput.vue`](https://github.com/vuejs/core/blob/main/CustomInput.vue):**

```javascript
import { defineComponent, toRefs } from 'vue'

export default defineComponent({
  props: {
    modelValue: String,
    checked: Boolean
  },
  emits: ['update:modelValue', 'update:checked'],
  setup(props, { emit }) {
    const { modelValue, checked } = toRefs(props)

    const updateValue = (val) => emit('update:modelValue', val)
    const updateChecked = (val) => emit('update:checked', val)

    return { modelValue, checked, updateValue, updateChecked }
  }
})

```

These examples confirm that `v-model` never accesses the prop directly; it only wires the prop and the event together to achieve two-way reactivity.

## Summary

- **`v-model`** is a directive in [`packages/runtime-core/src/directives/vModel.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/directives/vModel.ts) that provides syntactic sugar for two-way bindings.
- **`modelValue`** is a conventional prop name defined in [`packages/runtime-core/src/component.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/component.ts) that receives the bound value.
- The directive automatically compiles to `:modelValue` and `@update:modelValue` (or custom equivalents when using `v-model:propName`).
- Custom prop names allow multiple `v-model` bindings on a single component using the `v-model:customProp` syntax.
- The child component must declare both the prop and the corresponding `update:` event in its `emits` option.

## Frequently Asked Questions

### Is `modelValue` a reserved or magical property in Vue 3?

No. `modelValue` is merely a naming convention adopted by the `v-model` directive when no custom argument is provided. It functions identically to any other component prop and receives no special treatment from the reactivity system beyond what standard prop validation provides.

### Can I use multiple v-model bindings on a single component?

Yes. Vue 3 supports multiple `v-model` bindings using the argument syntax. You can declare `v-model:title="postTitle"` and `v-model:content="postContent"` simultaneously, provided the child component defines both `title` and `content` props and emits both `update:title` and `update:content` events.

### Do I need to declare `update:modelValue` in the emits option?

Yes, explicit declaration is recommended. While Vue 3 will still function if you emit undeclared events, declaring `update:modelValue` (or your custom update event) in the `emits` option enables better IDE support, type checking, and avoids warnings during development regarding unexpected events.

### Where does the v-model directive handle DOM elements versus components?

The implementation in [`packages/runtime-core/src/directives/vModel.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/directives/vModel.ts) includes separate handling paths for native form elements (input, select, textarea) and custom components. For native elements, it directly attaches event listeners to the DOM node; for components, it relies on the emitted `update:modelValue` event mechanism described above.