Kapitel 10 von 11

Vue 3 Tutorial: Formulare mit v-model binden und validieren

geprüft am 2 September 2026 · 5 Min.

Schnelle Antwort

v-model stellt eine Zwei-Wege-Bindung zwischen einem Formularfeld und einer Datenquelle her. Der Modifikator .number wandelt die Eingabe in eine Zahl um, .trim entfernt Leerzeichen, und @submit.prevent verhindert das Neuladen der Seite beim Absenden.

Am Ende dieses Kapitels, baust du ein vollständiges Formular, validierst es und gibst sein Ergebnis an die Elternkomponente weiter.

v-bind bindet die Daten an das Template, aber nur in eine Richtung. Ein Formular braucht auch die Gegenrichtung: Was der Benutzer tippt, muss zurück in den State. Genau dafür ist v-model da.

Das CSS des Formulars

Ergänze diese Regeln in src/assets/boutique.css:

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; }

Die Formularkomponente

Lege src/components/ReviewForm.vue an. Jedes Feld hängt über v-model an einer Ref:

src/components/ReviewForm.vue
<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>

Tippe in das Feld „Nom“ und beobachte name in den Vue-Devtools: Der Wert folgt jedem Tastendruck.

Was v-model tut

v-model ist keine Magie, sondern eine Abkürzung. Auf einem <input> sind diese beiden Zeilen gleichwertig:

html
<input v-model="name">

<input :value="name" @input="name = $event.target.value">

Eine Bindung nach unten, ein Listener nach oben. Vue wählt Attribut und Event je nach Element: value und input beim Textfeld, checked und change bei der Checkbox, value und change beim Select.

Die Modifikatoren

Drei Suffixe ersparen dir wiederkehrenden Code:

  • .number wandelt die Eingabe in eine Zahl um. Ohne ihn liefern ein <select> und ein <input type="number"> einen String: rating wäre "5", und ein Test === 5 würde fehlschlagen.
  • .trim entfernt Leerzeichen am Anfang und am Ende der Eingabe.
  • .lazy synchronisiert beim Event change statt bei jedem Tastendruck.
html
<input id="name" v-model.trim="name">
<select id="rating" v-model.number="rating">

Absenden

Ein HTML-Formular lädt die Seite beim Absenden neu. Der Modifikator .prevent aus Kapitel 5 verhindert das:

src/components/ReviewForm.vue
<form class="review-form" @submit.prevent="onSubmit">

Die Funktion validiert, schickt die Bewertung an die Elternkomponente und leert dann die Felder:

src/components/ReviewForm.vue
<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>

Gib die Fehlermeldung im Template aus, direkt vor dem Button:

src/components/ReviewForm.vue
<p v-if="error" class="review-error" role="alert">{{ error }}</p>
Warum kein alert()?

Ein alert()-Dialog blockiert die Seite, wird von einem Screenreader nicht zum richtigen Zeitpunkt vorgelesen und lässt sich nicht testen. Ein Absatz mit role="alert" wird von assistiven Technologien angesagt, sobald er erscheint, und bleibt während der Korrektur sichtbar.

Die Bewertungen anzeigen

Lege src/components/ReviewList.vue an, die die Liste als Prop bekommt:

src/components/ReviewList.vue
<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 zusammensetzen

ProductDisplay.vue wird zur Elternkomponente der beiden neuen Komponenten: Sie hält die Bewertungen und hört auf das Formular, über das Auslösen von Events aus dem vorigen Kapitel.

src/components/ProductDisplay.vue
<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>

Das v-if="reviews.length" verhindert einen leeren Kasten, solange noch keine Bewertung abgegeben wurde.

Schick das Formular leer ab: Die Fehlermeldung erscheint. Füll es aus: Die Bewertung landet in der Liste und die Felder leeren sich.

Beim Neuladen sind die Bewertungen weg, denn sie leben nur im Speicher. Wer sie behalten will, braucht eine Datenbank: siehe Firebase mit Vue.

v-model auf einer eigenen Komponente

v-model funktioniert auch auf deinen eigenen Komponenten. Seit Vue 3.4 erledigt das Makro defineModel das in einer Zeile:

src/components/ChampTexte.vue
<script setup>
const valeur = defineModel()
</script>

<template>
  <input :value="valeur" @input="valeur = $event.target.value">
</template>

Die Elternkomponente benutzt sie dann wie ein natives Feld:

html
<ChampTexte v-model="name" />

Die Anwendung ist fertig

Sie zeigt ein Produkt mit seinen Varianten, reagiert auf den Mauszeiger, kümmert sich um die Verfügbarkeit, füllt einen Warenkorb und nimmt geprüfte Bewertungen entgegen. Das nächste Kapitel setzt den letzten Schliff: die Animationen beim Ein- und Ausblenden.

In der Options API

v-model und seine Modifikatoren schreiben sich genauso. Die Felder werden in data() deklariert, die Validierung steht in methods, ausgelöst wird mit this.$emit.

ÜbungErgänze eine Checkbox „Ich empfehle dieses Produkt“, per v-model an einen Boolean gebunden, gib sie in der ausgelösten Bewertung mit und zeige sie in der Liste an.

Häufige Fehler

.number bei einer Bewertungsnote vergessen Ein <select> und ein <input type="number"> liefern einen String. Ohne .number schlägt ein Test === 5 fehl, und eine Rechnung verkettet, statt zu addieren.
.prevent am Formular vergessen Die Seite lädt beim Absenden neu und der State ist verloren. Schreib @submit.prevent="onSubmit".
Mit alert() validieren Der Dialog blockiert die Seite und wird von Screenreadern nicht richtig angesagt. Gib eine Meldung mit role="alert" aus.
Newsletter

Neue Tests, Tutorials und Projekte, per E-Mail.

Reproduzierbare Tests, versionierter Code, datierte Ergebnisse. Niemals Spam.