v-model in Vue.js
What is v-model?
v-model is a Vue directive that creates two-way data binding between form elements and component data.
<template>
<input v-model="message" />
<p>Message: {{ message }}</p>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello')
</script>
When user types text, message updates automatically, and vice versa.
How v-model Works
v-model is syntactic sugar over value binding and event handling:
<!-- With v-model -->
<input v-model="text" />
<!-- Equivalent to -->
<input
:value="text"
@input="text = $event.target.value"
/>
Vue automatically:
- Binds value to data (
:value) - Listens for change event (
@input) - Updates data on change
v-model with Different Elements
Input (text)
<template>
<input v-model="text" type="text" />
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>
Textarea
<template>
<textarea v-model="message"></textarea>
<p>{{ message }}</p>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Multi-line text')
</script>
Checkbox (single)
<template>
<input type="checkbox" v-model="checked" />
<span>Agreed: {{ checked }}</span>
</template>
<script setup>
import { ref } from 'vue'
const checked = ref(false)
</script>
Checkbox (multiple)
<template>
<input type="checkbox" value="Vue" v-model="frameworks" />
<input type="checkbox" value="React" v-model="frameworks" />
<input type="checkbox" value="Angular" v-model="frameworks" />
<p>Selected: {{ frameworks }}</p>
</template>
<script setup>
import { ref } from 'vue'
const frameworks = ref([])
</script>
Radio
<template>
<input type="radio" value="male" v-model="gender" />
<input type="radio" value="female" v-model="gender" />
<p>Gender: {{ gender }}</p>
</template>
<script setup>
import { ref } from 'vue'
const gender = ref('male')
</script>
Select
<template>
<select v-model="selected">
<option disabled value="">Choose</option>
<option value="A">Option A</option>
<option value="B">Option B</option>
<option value="C">Option C</option>
</select>
<p>Selected: {{ selected }}</p>
</template>
<script setup>
import { ref } from 'vue'
const selected = ref('')
</script>
v-model Modifiers
.lazy
By default, v-model updates on input event. The .lazy modifier switches to change:
<template>
<!-- Updates only after blur -->
<input v-model.lazy="message" />
</template>
.number
Automatically converts value to number:
<template>
<input v-model.number="age" type="number" />
</template>
<script setup>
import { ref } from 'vue'
const age = ref(0)
// age is always a number, not a string
</script>
.trim
Automatically trims whitespace:
<template>
<input v-model.trim="username" />
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
</script>
Combining Modifiers
<template>
<input v-model.lazy.trim="message" />
<input v-model.number.lazy="age" type="number" />
</template>
v-model in Custom Components
Composition API
In Composition API, v-model works with modelValue prop and update:modelValue event:
<!-- ParentComponent.vue -->
<template>
<CustomInput v-model="searchText" />
</template>
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const searchText = ref('')
</script>
<!-- CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
Using Computed for Simplification
<!-- CustomInput.vue -->
<template>
<input v-model="value" />
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
Multiple v-model (Vue 3)
In Vue 3, you can use multiple v-model on one component:
<!-- UserForm.vue -->
<template>
<UserName
v-model:first-name="firstName"
v-model:last-name="lastName"
/>
</template>
<script setup>
import { ref } from 'vue'
const firstName = ref('')
const lastName = ref('')
</script>
<!-- UserName.vue -->
<template>
<input
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
placeholder="First Name"
/>
<input
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
placeholder="Last Name"
/>
</template>
<script setup>
defineProps(['firstName', 'lastName'])
defineEmits(['update:firstName', 'update:lastName'])
</script>
Custom Modifiers
You can create custom modifiers:
<!-- Parent.vue -->
<template>
<MyComponent v-model.capitalize="myText" />
</template>
<!-- MyComponent.vue -->
<template>
<input
:value="modelValue"
@input="handleInput"
/>
</template>
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: {
default: () => ({})
}
})
const emit = defineEmits(['update:modelValue'])
function handleInput(event) {
let value = event.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
Common Mistakes
Mutating props directly
<!-- Wrong -->
<template>
<input v-model="modelValue" />
</template>
<script setup>
defineProps(['modelValue'])
// Vue will warn about mutating props
</script>
<!-- Correct -->
<template>
<input v-model="value" />
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
</script>
Conclusion
v-model:
- Creates two-way data binding
- Syntactic sugar over
:valueand@input - Works with different form elements
- Has modifiers:
.lazy,.number,.trim - In Vue 3 supports multiple usage
- Can be used in custom components
In interviews:
Important to be able to:
- Explain what v-model is and how it works
- Show how v-model expands to :value + @input
- Describe modifiers
- Explain how to use v-model in custom components
- Give examples with different form elements