VueJS - Digging into v-model

A cheat-sheet for what the VueJS "v-model" syntactic sugar is doing under the hood so we can customize the behavior of our components.
, Updated: 3 min read

Vue.js has a v-model attribute which is pretty handy for quickly and easily binding HTML input elements to JavaScript variables. Eventually you might wonder - what is v-model doing under the hood? What is it syntactic sugar for? Or, you might be trying to build a stateless component and v-model doesn’t exactly work nicely with that pattern.

A Simple Example

In this example, we replace v-model with the :value prop and @input event for a textbox. You can easily customize what happens when the input changes by modifying the event handler:

<!-- v-model syntax -->
<input v-model="x" />

<!-- Equivalent using props / events -->
<input :value="x" @input="x = $event.target.value" />

Whether you’re using single-file components or the newer composition API, this is still applicable. To get the most out of this page, should already be familiar with the v-model section of the Vue.JS documentation: https://vuejs.org/guide/essentials/forms#form-input-bindings

In this document, I’m using $event for brevity, but you can use an arrow function as an event handler. For example, @input="x = $event.target.value" can be written as @input="event => x = event.target.value".

HTML Inputs

Bindings for traditional HTML input elements such as checkboxes, radio buttons, text inputs, etc.

Text Input / Text Area

This works for both text inputs and textarea elements.

<!-- v-model syntax -->
<input v-model="x" />

<!-- Equivalent using Props -->
<input :value="x" @input="x = $event.target.value" />

Checkbox

A checkbox is very similar to a text input, except we’re using the checked attribute instead of value to get the boolean state of the checkbox.

<!-- v-model syntax -->
<input type="checkbox" v-model="x" />

<!-- Equivalent using Props -->
<input type="checkbox" :checked="x" @change="x = $event.target.checked" />

Radio Buttons

Since radio buttons are typically grouped and when selected they emit a specific value, we need to determine checked depending on whether or not our variable equals the value for the radio button. After, when the radio button is selected, we need to update our variable to the value of the radio button.

Technically, you don’t need value, and can do the same thing as the above checkbox example, but if trying to mimic a traditional HTML input this is likely what you’ll need.

<!-- v-model syntax -->
<input type="radio" value="A" v-model="x" />
<input type="radio" value="B" v-model="x" />

<!-- Equivalent using Props -->
<input
  type="radio"
  value="A"
  :checked="x === 'A'"
  @change="x = $event.target.value"
/>
<input
  type="radio"
  value="B"
  :checked="x === 'B'"
  @change="x = $event.target.value"
/>

Select Dropdown

<!-- v-model syntax -->
<select v-model="x">
  <option value="a">A</option>
  <option value="b">B</option>
  <option value="c">C</option>
</select>

<!-- Equivalent using Props -->
<select :value="x" @change="x = $event.target.value">
  <option value="a">A</option>
  <option value="b">B</option>
  <option value="c">C</option>
</select>

Custom Components

In Vue.js 2.0, the prop used by v-model for custom compoents was called value and the event it emitted on changes was the @input event.

Now, in Vue 3, it’s called modelValue, and the event is @update:modelValue. Note <input> tags, etc. still use the value prop as that matches how you interact with the element using its vanilla JS property.

<!-- v-model syntax -->
<MyComponent v-model="x" />

<!-- in VueJS 2.0, this is what this is equivalent to -->
<!-- *WILL NOT WORK IN VUE 3.0* -->
<MyComponent :value="x" @input="x = $event" />

<!-- in VueJS 3.0, this is what this is equivalent to -->
<MyComponent :modelValue="x" @update:modelValue="x = $event" />

Binding Multiple Values

Vue 3.0 also supports multiple bindings. Any prop that confirms to the propName / @update:propName pattern can be used with v-model. This is useful for components that have multiple values to bind to. For example, if my prop name is a, I can use v-model:a="someVar" to bind to it.

<!-- v-model syntax -->
<SomeCustomComponent v-model:a="someVar" v-model:b="someOtherVar" />

<!-- Equivalent using Props -->
<SomeCustomComponent
  :a="someVar"
  :b="someOtherVar"
  @update:a="someVar = $event"
  @update:b="someOtherVar = $event"
/>

Modifiers

Vue also supports modifiers for v-model to handle different input types. Here’s how you can handle them using props:

Number Binding - v-model.number

This coerces the input value to a number. .number is automatically applied when an input’s type is number. This conversion is only done if the number is able to be parsed. So, using this on a textarea won’t force the value to be a number.

<!-- v-model syntax -->
<input type="number" v-model="x" />

<!-- Equivalent using Props -->
<input type="number" :value="x" @input="event => {
  const parsedValue = parseFloat(event.target.value)
  x = isNaN(value) ? event.target.value : parsedValue
}" />

Trimmed Strings - v-model.trim

This trims the input value before assigning it

<!-- v-model syntax -->
<input v-model.trim="x" />

<!-- Equivalent using Props -->
<input :value="x" @input="x = $event.target.value.trim()" />

Lazy Updates - v-model.lazy

This updates the value after the @change event, rather than on every @input event.

<!-- v-model syntax -->
<input v-model.lazy="x" />

<!-- Equivalent using Props -->
<input :value="x" @change="x = $event.target.value" />

Subscribe to my Newsletter

Like this post? Subscribe to get notified for future posts like this.

Change Log

  • 5/1/2024 - Initial Revision
  • 6/11/2024 - Update introduction to be more clear.

Found a typo or technical problem? file an issue!