Chapitre 10 sur 11

Tutoriel VueJS 3 – Formulaires et v-model

vérifié le 2 septembre 2026 · 5 min

Réponse rapide

v-model crée une liaison à double sens entre un champ de formulaire et une donnée. Le modificateur .number convertit la saisie en nombre, .trim retire les espaces, et @submit.prevent empêche le rechargement de la page à l’envoi.

À la fin de ce chapitre, vous saurez construire un formulaire complet, le valider et transmettre son résultat au parent.

v-bind lie la donnée au gabarit, dans un seul sens. Un formulaire a besoin de l’autre sens aussi : ce que l’utilisateur tape doit remonter dans l’état. C’est le rôle de v-model.

Le CSS du formulaire

Ajoutez ces règles à 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; }

Le composant de formulaire

Créez src/components/ReviewForm.vue. Chaque champ est relié à une référence par v-model :

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>

Tapez dans le champ « Nom » et regardez name dans les outils de développement Vue : la valeur suit la frappe.

Ce que fait v-model

v-model n’est pas de la magie, c’est un raccourci. Sur un <input>, ces deux lignes sont équivalentes :

html
<input v-model="name">

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

Une liaison qui descend, un écouteur qui remonte. Vue adapte l’attribut et l’événement selon l’élément : value et input pour un champ texte, checked et change pour une case à cocher, value et change pour une liste déroulante.

Les modificateurs

Trois suffixes évitent du code répétitif :

  • .number convertit la saisie en nombre. Sans lui, un <select> et un <input type="number"> renvoient une chaîne : rating vaudrait "5", et un test === 5 échouerait.
  • .trim retire les espaces en début et fin de saisie.
  • .lazy synchronise sur l’événement change plutôt que sur chaque frappe.
html
<input id="name" v-model.trim="name">
<select id="rating" v-model.number="rating">

Soumettre

Un formulaire HTML recharge la page à l’envoi. Le modificateur .prevent vu au chapitre 5 l’en empêche :

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

La fonction valide, émet l’avis vers le parent, puis vide les champs :

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>

Affichez le message d’erreur dans le gabarit, juste avant le bouton :

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

Une boîte alert() bloque la page, n’est pas lisible par un lecteur d’écran au bon moment et ne se teste pas. Un paragraphe portant role="alert" est annoncé par les technologies d’assistance dès qu’il apparaît, et reste visible pendant la correction.

Afficher les avis

Créez src/components/ReviewList.vue, qui reçoit la liste en prop :

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>

Assembler

ProductDisplay.vue devient le parent des deux nouveaux composants : il stocke les avis et écoute le formulaire, avec l’émission d’événements du chapitre précédent.

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>

Le v-if="reviews.length" évite d’afficher un cadre vide tant qu’aucun avis n’a été déposé.

Envoyez le formulaire vide : le message d’erreur apparaît. Remplissez-le : l’avis s’ajoute à la liste et les champs se vident.

Les avis disparaissent au rechargement, puisqu’ils ne vivent qu’en mémoire. Pour les conserver, il faut une base : voyez Firebase avec Vue.

v-model sur un composant

v-model fonctionne aussi sur vos propres composants. Depuis Vue 3.4, la macro defineModel rend cela immédiat :

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

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

Le parent l’utilise alors comme un champ natif :

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

Vous avez terminé l’application

Elle affiche un produit et ses déclinaisons, réagit au survol, gère la disponibilité, remplit un panier et accepte des avis validés. Le chapitre suivant ajoute la dernière touche : les animations d’entrée et de sortie.

En Options API

v-model et ses modificateurs s’écrivent de la même façon. Les champs se déclarent dans data(), la validation dans methods, et l’émission par this.$emit.

ExerciceAjoutez une case à cocher « Je recommande ce produit » liée par v-model à un booléen, transmettez-la dans l’avis émis et affichez-la dans la liste.

Erreurs fréquentes

Oublier .number sur une note Un <select> et un <input type="number"> renvoient une chaîne. Sans .number, un test === 5 échoue et un calcul concatène au lieu d’additionner.
Oublier .prevent sur le formulaire La page se recharge à l’envoi et l’état est perdu. Écrivez @submit.prevent="onSubmit".
Valider avec alert() La boîte bloque la page et n’est pas annoncée correctement par les lecteurs d’écran. Affichez un message avec role="alert".
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.