Vue 3-tutorial: formulieren binden en valideren met v-model
geverifieerd op 2 september 2026 · 5 min
v-model legt een tweerichtingsbinding tussen een formulierveld en een stuk data. De modifier .number zet de invoer om naar een getal, .trim haalt de spaties weg, en @submit.prevent voorkomt dat de pagina bij het versturen herlaadt.
v-bind koppelt de data aan de template, in één richting. Een formulier heeft ook de andere richting nodig: wat de gebruiker intikt, moet terug in de state komen. Dat is de taak van v-model.
De CSS van het formulier
Voeg deze regels toe aan src/assets/boutique.css:
.review-form { display: flex; flex-direction: column; width: 425px; padding: 20px; border: 2px solid #d8d8d8; background: #fff; }
.review-container { width: 425px; padding: 20px; border: 2px solid #d8d8d8; background: #fff; }
.review-error { color: #b91c1c; }De formuliercomponent
Maak src/components/ReviewForm.vue aan. Elk veld hangt via v-model aan een ref:
<script setup>
import { ref } from 'vue'
const name = ref('')
const review = ref('')
const rating = ref(null)
</script>
<template>
<form class="review-form">
<h3>Laisser un avis</h3>
<label for="name">Nom</label>
<input id="name" v-model="name">
<label for="review">Avis</label>
<textarea id="review" v-model="review"></textarea>
<label for="rating">Note</label>
<select id="rating" v-model.number="rating">
<option disabled :value="null">Choisir une note</option>
<option>5</option>
<option>4</option>
<option>3</option>
<option>2</option>
<option>1</option>
</select>
<button class="button" type="submit">Envoyer</button>
</form>
</template>Tik iets in het veld “Nom” en houd name in de gaten in de Vue-devtools: de waarde volgt elke toetsaanslag.
Wat v-model doet
v-model is geen magie, het is een afkorting. Op een <input> zijn deze twee regels equivalent:
<input v-model="name">
<input :value="name" @input="name = $event.target.value">Een binding die naar beneden gaat, een listener die naar boven gaat. Vue kiest attribuut en event op basis van het element: value en input voor een tekstveld, checked en change voor een checkbox, value en change voor een select.
De modifiers
Drie achtervoegsels besparen je herhaald werk:
.numberzet de invoer om naar een getal. Zonder die modifier geven een<select>en een<input type="number">een string terug:ratingzou"5"zijn, en een test met=== 5zou mislukken..trimhaalt de spaties aan het begin en het einde van de invoer weg..lazysynchroniseert op het eventchangein plaats van bij elke toetsaanslag.
<input id="name" v-model.trim="name">
<select id="rating" v-model.number="rating">Versturen
Een HTML-formulier herlaadt de pagina bij het versturen. De modifier .prevent uit hoofdstuk 5 houdt dat tegen:
<form class="review-form" @submit.prevent="onSubmit">De functie valideert, stuurt de review naar de ouder en maakt daarna de velden leeg:
<script setup>
import { ref } from 'vue'
const emit = defineEmits(['review-submitted'])
const name = ref('')
const review = ref('')
const rating = ref(null)
const error = ref('')
function onSubmit() {
if (name.value === '' || review.value === '' || rating.value === null) {
error.value = 'Avis incomplet. Merci de remplir tous les champs.'
return
}
emit('review-submitted', {
name: name.value,
review: review.value,
rating: rating.value
})
name.value = ''
review.value = ''
rating.value = null
error.value = ''
}
</script>Toon de foutmelding in de template, vlak voor de knop:
<p v-if="error" class="review-error" role="alert">{{ error }}</p>Een alert()-venster blokkeert de pagina, wordt door een schermlezer niet op het juiste moment voorgelezen en laat zich niet testen. Een paragraaf met role="alert" wordt door hulptechnologie aangekondigd zodra hij verschijnt, en blijft zichtbaar terwijl de bezoeker corrigeert.
De reviews tonen
Maak src/components/ReviewList.vue aan, die de lijst als prop ontvangt:
<script setup>
defineProps({
reviews: { type: Array, required: true }
})
</script>
<template>
<div class="review-container">
<h3>Avis</h3>
<ul>
<li v-for="(review, index) in reviews" :key="index">
{{ review.name }} a mis {{ review.rating }} étoiles
<br>
« {{ review.review }} »
</li>
</ul>
</div>
</template>Alles samenvoegen
ProductDisplay.vue wordt de ouder van de twee nieuwe componenten: hij bewaart de reviews en luistert naar het formulier, met het uitzenden van events uit het vorige hoofdstuk.
<script setup>
import ReviewForm from './ReviewForm.vue'
import ReviewList from './ReviewList.vue'
const reviews = ref([])
function addReview(review) {
reviews.value.push(review)
}
</script>
<template>
<div class="product-display">
<div class="product-container">
<!-- la fiche produit, inchangée -->
</div>
<ReviewList v-if="reviews.length" :reviews="reviews" />
<ReviewForm @review-submitted="addReview" />
</div>
</template>De v-if="reviews.length" voorkomt dat er een leeg kader verschijnt zolang niemand een review heeft achtergelaten.
Verstuur het lege formulier: de foutmelding verschijnt. Vul het in: de review komt in de lijst en de velden worden leeggemaakt.
De reviews verdwijnen bij het herladen, want ze bestaan alleen in het geheugen. Om ze te bewaren heb je een database nodig: zie Firebase met Vue.
v-model op een component
v-model werkt ook op je eigen componenten. Sinds Vue 3.4 maakt de macro defineModel dat meteen mogelijk:
<script setup>
const valeur = defineModel()
</script>
<template>
<input :value="valeur" @input="valeur = $event.target.value">
</template>De ouder gebruikt hem dan als een gewoon veld:
<ChampTexte v-model="name" />De applicatie is af
Ze toont een product met zijn varianten, reageert op hover, houdt de voorraad bij, vult een winkelmandje en neemt gevalideerde reviews aan. Het volgende hoofdstuk zet er de laatste hand aan: de animaties bij het verschijnen en verdwijnen.
v-model en zijn modifiers schrijf je precies hetzelfde. De velden declareer je in data(), de validatie in methods, en het uitzenden gebeurt met this.$emit.
v-model aan een boolean hangt, geef hem mee in de uitgezonden review en toon hem in de lijst.Veelgemaakte fouten
<select> en een <input type="number"> geven een string terug. Zonder .number mislukt een test met === 5 en plakt een berekening de waarden aan elkaar in plaats van op te tellen.@submit.prevent="onSubmit".role="alert".