Hack Frontend Community

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:

  1. Binds value to data (:value)
  2. Listens for change event (@input)
  3. 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 :value and @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