Wat is two-way binding?
Bij een form-element heb je twee stromingen van data:
- Van JS naar input: de waarde van je ref tonen in het veld
- Van input naar JS: als de gebruiker typt, update de ref
Met v-model regelt Vue beide kanten in één regel. Geen aparte @input handler nodig.
Zonder v-model (omslachtig)
<input :value="name" @input="name = $event.target.value" />
Met v-model (zo doe je het)
<input v-model="name" />
Vue vs React: in React schrijf je voor élke input een onChange handler. In Vue is v-model alles wat je nodig hebt. Dit scheelt heel veel code in formulieren.
Tekst-inputs
v-model werkt op alle tekst-achtige inputs:
<script setup>
import { ref } from 'vue'
const name = ref('')
const password = ref('')
const age = ref(0)
</script>
<template>
<input v-model="name" type="text" placeholder="Naam" />
<input v-model="password" type="password" />
<input v-model="age" type="number" />
<p>Hallo {{ name }}, je bent {{ age }} jaar oud</p>
</template>
Let op bij type="number": de waarde is een string, niet een number. Wil je rekenen? Gebruik Number(age.value) of een computed.
Textarea
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
<template>
<textarea v-model="message" rows="4"></textarea>
<p>Je bericht: {{ message }}</p>
</template>
Fout:
<textarea v-model="message">{{ message }}</textarea> <!-- ❌ -->
Zet de waarde niet tussen de tags. v-model doet dat al voor je.
Checkbox
Een enkele checkbox bindt op een boolean (true / false):
<script setup>
import { ref } from 'vue'
const acceptTerms = ref(false)
</script>
<template>
<label>
<input v-model="acceptTerms" type="checkbox" />
Ik ga akkoord met de voorwaarden
</label>
<button :disabled="!acceptTerms">Verstuur</button>
</template>
Radio buttons
Bij een groep radio buttons bindt v-model op de value van de aangevinkte optie:
<script setup>
import { ref } from 'vue'
const size = ref('M')
</script>
<template>
<label>
<input v-model="size" type="radio" value="S" /> Small
</label>
<label>
<input v-model="size" type="radio" value="M" /> Medium
</label>
<label>
<input v-model="size" type="radio" value="L" /> Large
</label>
<p>Gekozen maat: {{ size }}</p>
</template>
Belangrijk: Alle radios in een groep gebruiken dezelfde v-model. Vue weet dan dat ze bij elkaar horen. Geen name-attribuut nodig.
Select (dropdown)
<script setup>
import { ref } from 'vue'
const country = ref('NL')
</script>
<template>
<select v-model="country">
<option value="NL">Nederland</option>
<option value="BE">België</option>
<option value="DE">Duitsland</option>
</select>
<p>Gekozen land: {{ country }}</p>
</template>
Met v-for opties
<script setup>
import { ref } from 'vue'
const selected = ref('')
const options = [
{ value: 'NL', label: 'Nederland' },
{ value: 'BE', label: 'België' },
{ value: 'DE', label: 'Duitsland' }
]
</script>
<template>
<select v-model="selected">
<option v-for="opt in options" :key="opt.value" :value="opt.value">
{{ opt.label }}
</option>
</select>
</template>
v-model op je eigen component
Je kunt v-model ook gebruiken op een component dat je zelf maakt. Dat doe je met defineModel().
Het child component
<!-- src/components/MyInput.vue -->
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" class="my-fancy-input" />
</template>
Het parent component
<!-- src/App.vue -->
<script setup>
import { ref } from 'vue'
import MyInput from './components/MyInput.vue'
const name = ref('')
</script>
<template>
<MyInput v-model="name" />
<p>Hallo {{ name }}!</p>
</template>
Wat doet defineModel()? Het maakt een two-way binding tussen parent en child. De ref model in het child gedraagt zich gewoon als een normale ref — verander hem, en de parent ziet de wijziging meteen.
Vue 3.4+: defineModel() is nieuw sinds Vue 3.4 (januari 2024). Kom je oudere code tegen met props.modelValue en emit('update:modelValue')? Dat is de oude, omslachtigere manier. Gebruik altijd defineModel().
Belangrijke Regels
v-model= two-way binding — vervangt:value+@input- Checkbox → boolean (true/false)
- Radio & select → de gekozen
value - Tekst-inputs → string (ook
type="number"geeft een string!) - Je eigen component → gebruik
defineModel()
Veelgemaakte Fouten
Fout — v-model én :value tegelijk:
<input v-model="name" :value="name" /> <!-- ❌ dubbel, conflict -->
Goed:
<input v-model="name" /> <!-- ✅ v-model regelt :value -->
Fout — waarde in textarea-content:
<textarea v-model="msg">{{ msg }}</textarea> <!-- ❌ -->
Goed:
<textarea v-model="msg"></textarea> <!-- ✅ -->
Fout — vergeten value bij radio:
<input v-model="size" type="radio" /> Small
<input v-model="size" type="radio" /> Medium
<!-- ❌ zonder value weet Vue niet welke gekozen is -->
Goed:
<input v-model="size" type="radio" value="S" /> Small
<input v-model="size" type="radio" value="M" /> Medium