100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

Form Input Bindings with v-model

Details how v-model implements two-way binding on form elements and components, what it compiles down to, and its modifiers and pitfalls.

Template DirectivesIntermediate8 min readJul 9, 2026
Analogies

Form Input Bindings with v-model

v-model provides two-way data binding on form inputs and on custom components. On a native <input>, <textarea>, or <select>, it is syntactic sugar that combines a :value binding with an @input (or @change, for checkboxes/radios/selects) listener that updates the bound ref. Rather than manually wiring both directions yourself, v-model="someRef" handles reading and writing in one directive. Crucially, v-model is not magic — it desugars to explicit prop-and-event wiring, and understanding that expansion is what makes v-model on custom components make sense.

🏏

Cricket analogy: v-model on a scorecard's run-entry field is like a scorer who both reads the current total and writes new runs in one motion, rather than a separate reader and a separate writer needing to be manually kept in sync on every ball.

What v-model compiles to

On a plain text input, <input v-model="searchTerm" /> expands to <input :value="searchTerm" @input="searchTerm = $event.target.value" />. This is why v-model requires a ref (or a reactive property) rather than a plain variable — the binding needs something it can reassign on every input event. On a checkbox, the desugaring uses :checked and toggles a boolean, or pushes/removes a value from an array if v-model is bound to an array of values across multiple checkboxes. On <select>, it binds :value and listens for change. Understanding this expansion also explains why v-model doesn't work on a destructured reactive property or a plain object field passed by value — the binding must resolve to something assignable.

🏏

Cricket analogy: Typing runs into a text field desugars to reading the value and writing it back on every keystroke, which is why v-model needs a ref it can reassign; on a wicket checkbox it toggles a boolean, and on a dismissal-type select it binds value and listens for change.

v-model on custom components

On a custom component, v-model="value" by default expands to a modelValue prop and an update:modelValue emitted event. The child component must declare defineProps(['modelValue']) and call emit('update:modelValue', newValue) whenever the value changes internally. You can also bind multiple v-models on the same component with named arguments — v-model:title="pageTitle" expands to a title prop and an update:title event — which lets a single component expose several independently two-way-bound fields.

🏏

Cricket analogy: v-model on a custom RunCounter component expands by default to a modelValue prop and an update:modelValue emit, so the child must declare the prop and emit the new count; a named v-model:overs lets the same component also expose overs as an independent two-way field.

vue
<!-- CurrencyInput.vue: a custom component supporting v-model -->
<script setup>
const props = defineProps({
  modelValue: { type: Number, required: true }
})
const emit = defineEmits(['update:modelValue'])

function onInput(event) {
  const parsed = parseFloat(event.target.value)
  emit('update:modelValue', Number.isNaN(parsed) ? 0 : parsed)
}
</script>

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

<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import CurrencyInput from './CurrencyInput.vue'

const price = ref(19.99)
</script>

<template>
  <CurrencyInput v-model="price" />
  <p>Current price: {{ price }}</p>
</template>

React has no built-in equivalent to v-model — two-way binding is always assembled manually with value={state} and onChange={e => setState(e.target.value)}. Vue's v-model is essentially that exact pattern with a directive that generates the boilerplate for you, and on custom components it standardizes the prop/event naming convention (modelValue / update:modelValue) so any two components adopting the convention interoperate predictably.

A common mistake is trying to v-model directly on a prop inside a child component (v-model="props.someProp"), which either throws a warning or silently fails, because props are meant to flow one-way from parent to child — mutating a prop directly violates Vue's one-way data flow and will be overwritten the next time the parent re-renders. The correct pattern is exactly the modelValue/update:modelValue (or a named v-model) convention shown above: the child never mutates the prop, it only emits an event asking the parent to update the source of truth.

  • v-model is sugar for a :value binding plus an event listener (@input, @change) that reassigns the bound ref.
  • On custom components, v-model defaults to a modelValue prop and an update:modelValue emitted event.
  • Named v-models (v-model:title) expand to a title prop and update:title event, enabling multiple independent bindings on one component.
  • v-model requires an assignable target (a ref or reactive property) — it cannot bind to a destructured value or a non-reactive plain variable.
  • Child components must never mutate a prop directly to implement v-model — they emit an update event and let the parent own the source of truth.
  • The exact DOM event v-model listens for depends on the element type: input for text fields, change for checkboxes/radios/selects.

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#VueJsStudyNotes#WebDevelopment#FormInputBindingsWithVModel#Form#Input#Bindings#Model#StudyNotes#SkillVeris