In Vue.js, the v-model directive allows you to create a two-way binding between an HTML form element and a JavaScript variable in the Vue instance.
Two-way binding works in both directions, from the data in JavaScript to the HTML and vice versa.
That is,
- Any change in the form element ➡️ will be reflected in the data
- Any change in the data ➡️ will be reflected in the form element.
In the previous tutorial, we saw one-way binding, for attributes, which works only from JS to HTML, and is done with the v-bind directive.
v-model Directive
As we said, the v-model directive allows for two-way binding that works between the JavaScript data and the HTML.
The binding occurs between a form element (like <input>, <textarea>, or <select>), which are the elements that can receive user data input.
Binding with v-model allows changes in the form element to be automatically reflected in the data, and vice versa.
<template>
<input v-model="message" placeholder="Escribe algo" />
<p>Mensaje: {{ message }}</p>
</template>
<script setup>
import { ref } from 'vue';
const message = ref('');
</script>
In this example:
- The text field value is bound to the
messagevariable. - If the user types in the field,
messageis automatically updated. - If
messagechanges in the script, the text field is automatically updated.
Under the hood v-model
<input v-model="message">
Is basically the same as a value binding and a handler for input
<input :value="message" @input="event => message = event.target.value">
Using v-model with Different Form Elements
v-model behaves (slightly) differently depending on the type of form element it is applied to.
Depending on the element we use it on, it will be,
| Element | Bound Attribute | Data Type | Behavior |
|---|---|---|---|
<input> (text) | value | String | The input value is bound to the property. Updates bidirectionally. |
<textarea> | value | String | The textarea content is bound to the property. Updates bidirectionally. |
<select> | value of the selected option | String (or array) | The selected value is bound to the property. In multiple, it’s an array. |
<input> (checkbox) | checked | Boolean (or array if multiple) | The checked state is bound to the property. In multiple, it’s an array. |
<input> (radio) | checked | String | The value of the selected option is bound to the property. Only one option can be selected. |
Vue handles the binding appropriately to ensure data is updated correctly in both directions.
<template>
<input v-model="text" type="text" placeholder="Escribe algo" />
<p>Texto ingresado: {{ text }}</p>
</template>
<script setup>
import { ref } from 'vue';
const text = ref('');
</script>
<template>
<textarea v-model="description" placeholder="Escribe una descripción"></textarea>
<p>Descripción: {{ description }}</p>
</template>
<script setup>
import { ref } from 'vue';
const description = ref('');
</script>
<template>
<select v-model="selectedOption">
<option value="opcion1">Opción 1</option>
<option value="opcion2">Opción 2</option>
<option value="opcion3">Opción 3</option>
</select>
<p>Opción seleccionada: {{ selectedOption }}</p>
</template>
<script setup>
import { ref } from 'vue';
const selectedOption = ref('opcion1');
</script>
<template>
<label>
<input type="checkbox" v-model="isChecked" />
¿Estás de acuerdo?
</label>
<p>Estado del checkbox: {{ isChecked ? 'Sí' : 'No' }}</p>
</template>
<script setup>
import { ref } from 'vue';
const isChecked = ref(false);
</script>
v-model Modifiers
v-model modifiers allow you to adjust the behavior of data synchronization between state and the DOM in Vue.js.
| Modifier | Description |
|---|---|
.lazy | Updates the value only after the change event occurs |
.number | Automatically converts the input value to a number |
.trim | Removes whitespace from the beginning and end of the value. |
The .lazy modifier makes v-model update the value only after the change event (instead of input).
<template>
<input v-model.lazy="message" placeholder="Escribe algo" />
<p>Mensaje: {{ message }}</p>
</template>
<script setup>
import { ref } from 'vue';
const message = ref('');
</script>
In this example:
- The value of
messageis updated only when the text field loses focus.
The .number modifier automatically converts the input to a number.
<template>
<input v-model.number="age" type="number" placeholder="Edad" />
<p>Edad: {{ age }} ({{ typeof age }})</p>
</template>
<script setup>
import { ref } from 'vue';
const age = ref(0);
</script>
In this example:
- The input is automatically converted to a number.
The .trim modifier removes whitespace from the beginning and end of the input.
<template>
<input v-model.trim="username" placeholder="Nombre de usuario" />
<p>Nombre de usuario: "{{ username }}"</p>
</template>
<script setup>
import { ref } from 'vue';
const username = ref('');
</script>
In this example:
- Whitespace at the beginning and end is automatically removed.
It is possible to combine several modifiers to achieve more precise behavior.
<input v-model.lazy.trim.number="amount" />
Using v-bind in Our Components Advanced
v-model can also be used in our own components to create a two-way binding between the parent component and the child component.
CustomInput.vue
<template>
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>
<script setup>
defineProps({
modelValue: String,
});
defineEmits(['update:modelValue']);
</script>
ParentComponent.vue
<template>
<CustomInput v-model="message" />
<p>Mensaje: {{ message }}</p>
</template>
<script setup>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
const message = ref('');
</script>
In this example:
- The child component (
CustomInput) receives themodelValueprop and emits anupdate:modelValueevent when the value changes. - The parent component uses
v-modelto bindmessageto the child component.
