Chapter 13: Vue v-model
Vue v-model directive.
Imagine you’re building a form — a login screen, a todo input, a settings page, a checkout — and every time the user types something, you have to manually update your data, and every time your data changes (from an API or button), you have to push it back to the input. That sounds exhausting, right?
v-model solves exactly this pain by giving you true two-way data binding out of the box — clean, automatic, and magical-feeling when you first see it.
In Vue 3 (which we’re using in 2026), v-model works on:
- Native form elements: <input>, <textarea>, <select>
- Custom components (very important for reusable form fields)
Today I’ll explain it like we’re pair-programming in VS Code — step by step, with lots of examples, modifiers, gotchas, and real patterns.
1. What v-model Really Does (Under the Hood)
For a normal <input type=”text”>, this:
|
0 1 2 3 4 5 6 |
<input v-model="message" /> |
…is just syntactic sugar for:
|
0 1 2 3 4 5 6 7 8 9 |
<input :value="message" @input="message = $event.target.value" /> |
- :value pushes data down to the input
- @input listens and pulls changes up to your data
Vue picks the right prop/event pair automatically depending on the element:
| Element | Prop used | Event used | Notes |
|---|---|---|---|
| <input type=”text”>, <textarea> | value | input | Default |
| <input type=”checkbox”>, <input type=”radio”> | checked | change | Boolean or array |
| <select> | value | change | String or array (multiple) |
| Custom component | modelValue (default) | update:modelValue | Vue 3 convention |
2. Basic Example – Text Input (Hello World Style)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<template> <div class="demo"> <h2>Live Preview</h2> <p>You typed: <strong>{{ message }}</strong></p> <input type="text" v-model="message" placeholder="Type something magical..." /> <button @click="message = 'Vue is awesome!'"> Reset to default </button> </div> </template> <script setup> import { ref } from 'vue' const message = ref('Hello from Hyderabad! 🌶️') </script> <style scoped> .demo { padding: 2rem; max-width: 500px; margin: auto; } input { width: 100%; padding: 0.8rem; font-size: 1.1rem; } </style> |
→ Type in the input → message updates instantly → Click button → input updates automatically That’s two-way binding!
3. Modifiers – Make v-model Even Smarter
Vue gives three built-in modifiers:
- .lazy → Update only on change (blur) instead of every keystroke
|
0 1 2 3 4 5 6 7 |
<input v-model.lazy="expensiveValue" /> <!-- Only updates when you tab out or press enter --> |
- .number → Automatically convert to number (great for age, quantity)
|
0 1 2 3 4 5 6 7 |
<input type="number" v-model.number="age" /> <!-- age becomes number, not string --> |
- .trim → Remove leading/trailing whitespace
|
0 1 2 3 4 5 6 7 |
<input v-model.trim="username" /> <!-- " rahul " → "rahul" --> |
You can combine them:
|
0 1 2 3 4 5 6 |
<input v-model.trim.lazy.number="quantity" /> |
4. Checkbox & Radio (Common & Tricky)
Single checkbox (boolean):
|
0 1 2 3 4 5 6 7 |
<input type="checkbox" v-model="agreed" /> <p v-if="agreed">You agreed to terms ✅</p> |
|
0 1 2 3 4 5 6 |
const agreed = ref(false) |
Multiple checkboxes (array):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
<div> <label v-for="fruit in fruits" :key="fruit"> <input type="checkbox" :value="fruit" v-model="favorites" /> {{ fruit }} </label> </div> <p>Favorites: {{ favorites.join(', ') }}</p> |
|
0 1 2 3 4 5 6 7 |
const fruits = ['Mango', 'Guava', 'Banana', 'Sapota'] const favorites = ref([]) |
Radio buttons:
|
0 1 2 3 4 5 6 7 8 9 |
<label><input type="radio" value="morning" v-model="timeOfDay" /> Morning</label> <label><input type="radio" value="evening" v-model="timeOfDay" /> Evening</label> <p>Selected: {{ timeOfDay }}</p> |
5. Select (Single & Multiple)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<select v-model="selectedCity"> <option disabled value="">Choose city</option> <option>Hyderabad</option> <option>Bengaluru</option> <option>Chennai</option> </select> <!-- Multiple --> <select multiple v-model="visitedCities"> <option>Delhi</option> <option>Mumbai</option> <option>Kolkata</option> </select> |
6. v-model on Custom Components (Super Important in 2026)
This is where v-model becomes really powerful for reusable form fields.
Modern pattern with defineModel() (recommended since Vue 3.4+ — cleanest way):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!-- CustomInput.vue --> <template> <div class="custom-field"> <label :for="id">{{ label }}</label> <input :id="id" :type="type" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" :placeholder="placeholder" /> </div> </template> <script setup> const modelValue = defineModel() // ← magic! creates prop + emit defineProps({ label: String, type: { type: String, default: 'text' }, placeholder: String }) const id = `field-${Math.random().toString(36).slice(2, 8)}` </script> |
Parent usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
<CustomInput v-model="form.email" label="Email Address" type="email" placeholder="you@company.com" /> <p>Email: {{ form.email }}</p> |
Before defineModel() (still works, more verbose):
|
0 1 2 3 4 5 6 7 |
const props = defineProps(['modelValue']) const emit = defineEmits(['update:modelValue']) |
7. Multiple v-model on One Component (Vue 3 Superpower)
You can have several:
|
0 1 2 3 4 5 6 7 8 9 10 |
<MyDatePicker v-model:start="startDate" v-model:end="endDate" v-model:timezone="tz" /> |
Child must emit update:start, update:end, etc.
Quick Summary Table – v-model in 2026
| Feature | Syntax / Pattern | Best For / Notes |
|---|---|---|
| Basic text | v-model=”name” | Simple inputs |
| Modifiers | .lazy .number .trim | Performance, type safety, clean data |
| Checkbox (single) | v-model=”isActive” | Boolean toggles |
| Checkbox (multiple) | v-model=”hobbies” + :value | Array collection |
| Custom component default | v-model=”value” | Uses modelValue + update:modelValue |
| Custom with defineModel() | const model = defineModel() | Cleanest modern way (3.4+) |
| Multiple bindings | v-model:foo=”a” v-model:bar=”b” | Date pickers, ranges, complex widgets |
Pro Tips from Real Projects
- Use reactive({}) for multi-field forms → easier than many refs
- For validation → combine with computed + HTML5 attributes or libraries (VeeValidate 4, Zod + custom composable)
- Avoid v-model on non-form elements — it’s meant for inputs/components
- In large forms → extract fields to components with v-model support
Practice: Build a quick contact form with:
- Name (trim)
- Email (type=email)
- Message (textarea)
- Checkbox “Subscribe to newsletter”
- Submit → console.log form data
Any part confusing? Want a full reactive form + validation example? Or v-model with file input? (special case) Or deep dive into custom component v-model with arguments?
Just tell me — we’ll keep going step by step 🚀
Happy binding from Hyderabad! 💙
