How to use v-model in Vue

By

Learn how to use the Vue v-model directive for two-way binding on form inputs, component modelValue props, and modifiers like .lazy, .trim, and .number.

~~~

The v-model Vue directive allows us to create a two-way binding. You put it on a form element, and Vue keeps the element and a data property in sync, in both directions. This guide is for Vue 3.

You can bind a form input element for example, and make it change the Vue data property when the user changes the content of the field:

<script setup>
import { ref } from 'vue'

const message = ref('')
</script>

<template>
  <input v-model="message" placeholder="Enter a message">
  <p>Message is: {{ message }}</p>
</template>

Type in the field, and the paragraph updates as you type. Change message in your code, and the field updates too. That’s the two-way part.

It works on select elements as well:

<select v-model="selected">
  <option disabled value="">Choose a fruit</option>
  <option>Apple</option>
  <option>Banana</option>
  <option>Strawberry</option>
</select>
<span>Fruit chosen: {{ selected }}</span>

And on checkboxes, where a single checkbox binds to a boolean:

<input type="checkbox" v-model="subscribed">

What v-model does under the hood

v-model is syntax sugar. For a text input, this:

<input v-model="message">

is the same as writing:

<input :value="message" @input="message = $event.target.value">

Vue binds the value attribute to the data property, and updates the property on every input event. Knowing this helps when something behaves unexpectedly: there’s no magic, just a prop and an event.

v-model on components

On a custom component, v-model binds to a prop named modelValue and listens for update:modelValue:

<!-- Parent -->
<CustomInput v-model="search" />

is the same as:

<CustomInput
  :modelValue="search"
  @update:modelValue="search = $event"
/>

Inside the child with <script setup>:

<script setup>
defineProps({
  modelValue: String
})

const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  >
</template>

Since Vue 3.4 you can skip the prop and the emit and use defineModel(). It gives you a ref that stays in sync with the parent, so you can put v-model on the inner input directly:

<script setup>
const model = defineModel()
</script>

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

You can also use multiple v-model bindings with arguments, like v-model:title="title", which expands to a title prop and an update:title event.

Handy directive modifiers

To make the model update when the change event occurs, and not any time the user presses a key, you can use v-model.lazy instead of just v-model.

Working with input fields, v-model.trim is useful because it automatically removes whitespace.

And if you accept a number instead than a string, make sure you use v-model.number. Without it, an input always gives you a string, and age + 1 turns "30" into "301".

Nested properties

Say you have a shopping cart, and you have a component that holds a form to add a product:

<template>
  <div>
    <h1>Add Product</h1>
    <label>Name</label>: <input v-model="product.name">
    <label>Description</label>: <textarea v-model="product.description"></textarea>
    <button @click="addProduct">Add</button>
  </div>
</template>

<script setup>
import { reactive } from 'vue'

const product = reactive({
  name: '',
  description: ''
})

function addProduct() {
  console.log(product)
}
</script>

To make the form update the inner properties of the product state value, you use product.* with v-model as above.

In Vue 3, reactivity is Proxy-based, so new properties added to a reactive object are tracked. You don’t need the old Vue 2 Vue.set workaround. Still, declaring the shape you bind to up front keeps the form clearer and avoids empty fields that start life as undefined.

If you’re new to wiring clicks on that Add button, see Vue methods.

Tagged: Vue.js · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about vue: