🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Two-Way Binding (v-model) in Vue.js

Learn about Two-Way Binding (v-model) in this comprehensive Vue.js tutorial. Understand two-way data binding, how v-model works on different input types, and its modifiers.

Total XP: 0|💻 vuejs XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

Select an unlocked node to view details root

🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

The easiest way to work with forms in JavaScript.

1What is Two-Way Binding?

Normally, data flows one way: from your JavaScript state into the HTML. Two-way binding means that if the HTML changes (e.g., a user types in an input), the JavaScript state updates automatically. And if the JavaScript state changes, the HTML updates.

2The `v-model` Directive

v-model is the magic directive that implements two-way binding on form input, textarea, and select elements. It automatically picks the correct way to update the element based on the input type.

3v-model Modifiers

Vue provides built-in modifiers: .lazy (syncs after 'change' event instead of 'input'), .number (typecasts input as a number), and .trim (strips whitespace).

4Step-by-Step Breakdown

Forms are a huge part of web development. In vanilla JS, syncing an input with a variable requires listening to the "input" event and manually updating the variable.

Vue makes this trivial with Two-Way Data Binding using the v-model directive.

Which directive is used in Vue to create two-way data bindings on form inputs?

  • v-bind
  • v-model
  • v-on

Why "Two-Way"?

1. If the user types, the message ref updates automatically.

2. If you change message.value in JS, the input field updates automatically.

v-model works magically on almost all form elements: text inputs, checkboxes, radio buttons, and select dropdowns.

If you use v-model on a <input type="checkbox">, what data type should the bound ref typically hold?

  • String
  • Number
  • Boolean

Vue also provides modifiers for v-model. For example, v-model.number automatically converts the user's input from a string to a Number.

Another modifier is v-model.trim, which automatically strips whitespace from the beginning and end of the input.

Which v-model modifier automatically removes whitespace from the beginning and end of the user input?

  • strip
  • trim
  • clean

Behind the scenes, v-model is just syntactic sugar for binding the value prop and listening to the input event. But it saves us writing so much code!

Level Up 🚀

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1v-model Doesn't Replace an Explicit Label

v-model wires an input's value to reactive state, but it does nothing for the input's accessible name — a text field bound with v-model="email" still needs an associated <label for="email"> or aria-label, since screen reader users have no way to know what the field is for based on its reactive binding alone.

<label for="email">Email</label> <input id="email" v-model="email" />

SEO Implications

  • 1

    v-model State Lives in JavaScript Memory, Not in the Rendered HTML Value Attribute

    During SSR, an <input v-model="query"> renders its current value into the HTML's value attribute, so a pre-filled search box is visible to a crawler fetching the raw page — but any value the user types afterward exists purely in the browser's reactive state and is never reflected back into a server-rendered document.

Best Practices

Use .lazy, .number, and .trim Instead of Manual Event Handling

Rather than writing @input="val => data = Number(val.target.value.trim())", express the same intent declaratively with v-model.lazy.number or v-model.trim — the modifiers keep the template readable and avoid re-implementing type coercion Vue already provides.

Frequent Bugs

THE BUG

Using v-model on a custom component without implementing the modelValue prop/event pair.

THE FIX

Plain v-model="x" on a custom component expects that component to accept a modelValue prop and emit an update:modelValue event; if the component uses a different prop name with no matching emit, the parent's v-model binding silently does nothing — the child never receives updates or reports its own changes back.

Real-World Examples

A Custom Currency Input Component

A <CurrencyInput v-model="price" /> wraps a native input, formats the displayed value with commas, but emits the raw numeric value via update:modelValue, so the parent's price ref always stays a clean number even though the visible text shows '$1,250.00'.

// CurrencyInput.vue
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
function onInput(e) {
  emit('update:modelValue', Number(e.target.value.replace(/[^0-9.]/g, '')));
}

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Mutating a Prop Directly

// Wrong const props = defineProps(['count']); function increment() { props.count++; } // Correct const emit = defineEmits(['update:count']); function increment() { emit('update:count', props.count + 1); }

The Solution //

A child component should never assign to a prop it received (e.g. props.title = 'New'). Vue logs "Unexpected mutation of prop" because props are a one-way binding. Copy the prop into local state with ref()/computed() first, or emit an event asking the parent to change its own data.

The Error //

Losing Reactivity by Destructuring reactive()

// Wrong const { name } = reactive({ name: 'Alice' }); // name is now a static string // Correct const state = reactive({ name: 'Alice' }); const { name } = toRefs(state); // name is a live ref

The Solution //

Destructuring a reactive() object copies its properties out as disconnected plain values, so future mutations of the original object won't update the destructured variables. Keep referencing the object's properties directly, or convert it with toRefs() before destructuring.

Continue Learning