Capitolo 10 di 11

Tutorial VueJS 3: gestire i form con v-model e validazione

verificato il 2 Settembre 2026 · 5 min

Risposta rapida

v-model crea un binding a doppio senso tra un campo di form e un dato. Il modificatore .number converte quello che si digita in numero, .trim toglie gli spazi, e @submit.prevent impedisce il ricaricamento della pagina all'invio.

Alla fine di questo capitolo, saprai costruire un form completo, validarlo e trasmetterne il risultato al componente genitore.

v-bind lega il dato al template, in un solo senso. Un form ha bisogno anche del senso opposto: quello che l’utente digita deve risalire nello stato. È il ruolo di v-model.

Il CSS del form

Aggiungi queste regole a 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; }

Il componente del form

Crea src/components/ReviewForm.vue. Ogni campo è collegato a una ref con 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>

Scrivi nel campo «Nome» e guarda name nei DevTools di Vue: il valore segue la digitazione.

Che cosa fa v-model

v-model non è magia, è una scorciatoia. Su un <input> queste due righe sono equivalenti:

html
<input v-model="name">

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

Un binding che scende, un listener che risale. Vue adatta l’attributo e l’evento all’elemento: value e input per un campo di testo, checked e change per una checkbox, value e change per una select.

I modificatori

Tre suffissi evitano codice ripetitivo:

  • .number converte quello che si digita in numero. Senza, un <select> e un <input type="number"> restituiscono una stringa: rating varrebbe "5", e un test === 5 fallirebbe.
  • .trim toglie gli spazi all’inizio e alla fine.
  • .lazy sincronizza sull’evento change invece che a ogni tasto premuto.
html
<input id="name" v-model.trim="name">
<select id="rating" v-model.number="rating">

Inviare

Un form HTML ricarica la pagina all’invio. Il modificatore .prevent visto al capitolo 5 glielo impedisce:

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

La funzione valida, emette la recensione verso il componente genitore, poi svuota i campi:

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>

Mostra il messaggio di errore nel template, subito prima del pulsante:

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

Una finestra alert() blocca la pagina, non viene letta al momento giusto da uno screen reader e non si testa. Un paragrafo con role="alert" viene annunciato dalle tecnologie assistive appena compare, e resta visibile per tutto il tempo della correzione.

Mostrare le recensioni

Crea src/components/ReviewList.vue, che riceve la lista come 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>

Assemblare

ProductDisplay.vue diventa il genitore dei due nuovi componenti: conserva le recensioni e ascolta il form, con l’emissione di eventi del capitolo precedente.

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>

Il v-if="reviews.length" evita di mostrare un riquadro vuoto finché nessuno ha lasciato una recensione.

Invia il form vuoto: compare il messaggio di errore. Compilalo: la recensione si aggiunge alla lista e i campi si svuotano.

Le recensioni spariscono al ricaricamento, dato che vivono solo in memoria. Per conservarle serve un database: vedi Firebase con Vue.

v-model su un componente

v-model funziona anche sui tuoi componenti. Da Vue 3.4 la macro defineModel lo rende immediato:

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

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

Il genitore lo usa allora come un campo nativo:

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

Hai finito l’applicazione

Mostra un prodotto e le sue varianti, reagisce al passaggio del mouse, gestisce la disponibilità, riempie un carrello e accetta recensioni validate. Il capitolo successivo aggiunge l’ultimo tocco: le animazioni di entrata e di uscita.

In Options API

v-model e i suoi modificatori si scrivono allo stesso modo. I campi si dichiarano in data(), la validazione in methods, e l’emissione con this.$emit.

EsercizioAggiungi una checkbox «Consiglio questo prodotto» legata con v-model a un booleano, trasmettila nella recensione emessa e mostrala nella lista.

Errori frequenti

Dimenticare .number su un voto Un <select> e un <input type="number"> restituiscono una stringa. Senza .number, un test === 5 fallisce e un calcolo concatena invece di sommare.
Dimenticare .prevent sul form La pagina si ricarica all'invio e lo stato è perso. Scrivi @submit.prevent="onSubmit".
Validare con alert() La finestra blocca la pagina e non viene annunciata correttamente dagli screen reader. Mostra un messaggio con role="alert".
Newsletter

I nuovi test, tutorial e progetti, via e-mail.

Test riproducibili, codice versionato, risultati datati. Mai spam.