04Lezione

Form HTML

Raccogliere dati dagli utenti: input, validazione e invio al server. Il ponte tra HTML statico e applicazioni dinamiche.

Introduzione

Un form HTML è una porzione di pagina web che permette agli utenti di inserire e inviare dati. Ogni volta che ci registriamo, scriviamo un commento, facciamo una ricerca o effettuiamo un acquisto online, stiamo usando un form.

A differenza delle prime tre lezioni, qui non parleremo di layout o stili: i form sono il ponte tra HTML statico e applicazioni dinamiche. In questa lezione vedremo come costruirli, come validare i dati e come questi viaggiano fino al server.

Form vs CSS

Le lezioni precedenti riguardavano la presentazione (Flexbox, Grid, Media Queries). Questa lezione riguarda l'interazione: come l'utente comunica col sito.

L'elemento <form>

Il tag <form> è il contenitore che raggruppa tutti i campi e definisce dove e come saranno inviati i dati. Al suo interno si inseriscono input, textarea, select e pulsanti.

Attributi principali

AttributoComportamento
actionURL della risorsa che riceverà i dati.
methodMetodo HTTP: get o post.
nameNome del form (utile via JavaScript).
autocompleteAbilita/disabilita autocompletamento (on/off).
novalidateDisabilita la validazione automatica del browser.
enctypeCodifica dei dati. Necessario per upload file (multipart/form-data).
targetDove aprire la risposta (_self, _blank, …).
Anatomia minima di un form

Ogni form serio ha sempre almeno: una <label>, un campo (<input>/<textarea>/<select>) e un pulsante di submit. Tutto il resto è opzionale.

Tipi di input

L'elemento <input> cambia comportamento drasticamente in base all'attributo type. Vediamoli per categorie.

Testuali — text, password, email, url, tel, search

Pur somigliandosi visivamente, alcuni di questi attivano una tastiera virtuale specifica su mobile e applicano una validazione automatica.

Mobile keyboards

Su smartphone, type="email" mostra la tastiera con il tasto @; type="tel" mostra un tastierino numerico; type="url" facilita l'inserimento di indirizzi web. Scegliere il type giusto è anche un'ottimizzazione UX.

Numerici — number, range

Si combinano con min, max e step per definire i vincoli. Lo slider qui sotto aggiorna live il valore mostrato.

Data/tempo — date, time, datetime-local, month, week

Mostrano un selettore (date picker) appropriato. Aspetto e supporto variano leggermente tra browser.

Selezione — checkbox e radio

La differenza fondamentale: checkbox = più scelte indipendenti; radio = scelta unica fra opzioni dello stesso gruppo. Per raggruppare radio button basta condividere lo stesso name.

Stesso name = stesso gruppo

Se due <input type="radio"> hanno name diversi, NON sono mutuamente esclusivi. Errore classico — il sintomo è “perché posso selezionarne due insieme?”.

File — type="file"

Permette il caricamento di uno o più file. accept filtra i tipi accettati, multiple abilita la selezione multipla.

html
<!-- una sola immagine -->
<input type="file" name="foto" accept="image/*">

<!-- più documenti PDF/DOCX -->
<input type="file" name="docs" accept=".pdf,.docx" multiple>
enctype obbligatorio

Per inviare file con un form serve impostare enctype="multipart/form-data" sul tag <form>. Lo vedremo nella sezione “Invio dei dati”.

Speciali — color, hidden

Il color picker apre il selettore nativo del sistema. Il colore scelto colora live il quadrato accanto.

html
<!-- Hidden: l'utente non lo vede, ma viene inviato -->
<input type="hidden" name="id_utente" value="42">

Azioni — submit, reset, button, image

Pulsanti realizzati tramite <input>. Sono ancora supportati per retrocompatibilità, ma nei progetti nuovi è preferibile il tag <button> (vedi sezione successiva).

html
<input type="submit" value="Invia">
<input type="reset"  value="Resetta">

Tabella riassuntiva

type="text"

Testo libero (default)

type="password"

Caratteri mascherati

type="email"

Validazione email + tastiera @

type="url"

Validazione URL

type="tel"

Tastiera numerica mobile

type="search"

Campo ricerca (con ×)

type="number"

Numeri con frecce ↑↓

type="range"

Slider grafico

type="date"

Selettore data

type="time"

Selettore ora

type="datetime-local"

Data + ora

type="month"

Mese e anno

type="week"

Settimana e anno

type="checkbox"

Selezione multipla

type="radio"

Selezione esclusiva

type="file"

Upload file

type="color"

Color picker

type="hidden"

Nascosto, ma inviato

type="submit"

Pulsante invio

type="reset"

Pulsante reset

Label e accessibilità

La <label> è uno degli elementi più sottovalutati e più importanti dei form. Ogni input dovrebbe avere una label associata.

  • Accessibilità — gli screen reader leggono la label all'utente quando il focus arriva sul campo.
  • Usabilità — cliccando sul testo della label il focus passa automaticamente al campo. Cruciale per checkbox e radio, che hanno aree cliccabili minuscole.
  • Chiarezza — il modulo è più comprensibile e professionale.

Metodo 1 — for / id (raccomandato)

html
<label for="email">Indirizzo email:</label>
<input type="email" id="email" name="email">

La label e l'input possono stare anche distanti nel codice, purché gli identificatori coincidano.

Metodo 2 — annidamento

html
<label>
  <input type="checkbox" name="newsletter"> Iscriviti alla newsletter
</label>

Non serve id/for. Particolarmente comodo per checkbox e radio.

placeholder ≠ label

Molti principianti pensano che il placeholder sostituisca la label. NO: il placeholder sparisce non appena si scrive, e gli screen reader spesso lo ignorano. Usate sempre una <label>.

Altri elementi dei form

<textarea> — testo multi-riga

Mentre <input type="text"> è limitato a una riga,<textarea> permette testo lungo su più righe. Va sempre chiusa con </textarea>; il valore iniziale si scrive fra i tag, non con value.

html
<label for="messaggio">Messaggio:</label>
<textarea id="messaggio" name="messaggio"
          rows="5" cols="40" maxlength="500"
          placeholder="Scrivi qui..."></textarea>

<select>, <option> e <optgroup>

Menu a tendina. L'attributo value di <option> è ciò che viene inviato; il testo visibile è solo per l'utente. Con liste lunghe conviene raggruppare in <optgroup>.

html
<label for="citta">Città:</label>
<select id="citta" name="citta">
  <optgroup label="Svizzera">
    <option value="lugano">Lugano</option>
    <option value="bellinzona" selected>Bellinzona</option>
    <option value="zurigo">Zurigo</option>
  </optgroup>
  <optgroup label="Italia">
    <option value="milano">Milano</option>
    <option value="roma">Roma</option>
  </optgroup>
</select>

<!-- Selezione multipla -->
<select name="lingue" multiple size="4">
  <option value="it" selected>Italiano</option>
  <option value="en">English</option>
  <option value="de">Deutsch</option>
</select>

<button> — pulsanti moderni

A differenza di <input type="submit">, può contenere HTML al suo interno (icone, testo formattato, ecc.). I tre type:

typeComportamento
submitInvia il form (default dentro un form).
resetRiporta i campi ai valori iniziali.
buttonPulsante generico, non fa nulla senza JS.
html
<button type="submit">Invia</button>
<button type="reset">Annulla</button>
<button type="button">Pulsante generico</button>
Specificate sempre il type

Dentro un <form>, un <button> senza type esplicito è di default submit. Questo causa invii accidentali (es. premendo Invio in un campo). Indicate sempre type="submit", type="reset" o type="button".

<fieldset> e <legend> — raggruppare campi

Quando un form ha molti campi, è utile organizzarli in gruppi tematici con <fieldset>. <legend>, posto come primo figlio, fornisce un titolo annunciato dagli screen reader come contesto per tutti i campi interni.

html
<form action="/registrazione" method="post">
  <fieldset>
    <legend>Dati personali</legend>
    <label for="nome">Nome:</label>
    <input type="text" id="nome" name="nome">

    <label for="cognome">Cognome:</label>
    <input type="text" id="cognome" name="cognome">
  </fieldset>

  <fieldset>
    <legend>Account</legend>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email">

    <label for="pwd">Password:</label>
    <input type="password" id="pwd" name="pwd">
  </fieldset>

  <button type="submit">Registrati</button>
</form>

Validazione HTML5

HTML5 offre attributi che fanno validare i dati al browser, prima ancora di inviarli al server. Se i dati non sono validi, il browser mostra un messaggio di errore e blocca l'invio.

Gli attributi di validazione

AttributoEffetto
requiredCampo obbligatorio.
type="email" / urlValidazione automatica del formato.
min / max / stepVincoli numerici e di data.
minlength / maxlengthVincoli sulla lunghezza del testo.
patternEspressione regolare (regex) custom.
titleTooltip esplicativo in caso di errore.

Prova a inviare il form qui sotto con campi vuoti o malformati: il browser mostra il messaggio di errore in italiano. Lo stile :user-invalid colora di rosso solo i campi che l'utente ha già toccato.

Pattern e regex (cenno)

L'attributo pattern richiede che il valore corrisponda a un'espressione regolare. Le regex sono un argomento ampio: per ora basti sapere che esistono e sono lo strumento per validazioni complesse.

html
<!-- esattamente 4 cifre (es. CAP svizzero) -->
<input type="text" name="cap" pattern="[0-9]{4}"
       title="Inserisci un CAP svizzero di 4 cifre" required>

<!-- targa svizzera tipo TI 12345 -->
<input type="text" name="targa" pattern="[A-Z]{2} [0-9]{1,6}">

Disabilitare la validazione: novalidate

html
<form action="/bozza" method="post" novalidate>
  <!-- la validazione è disabilitata, anche con required -->
</form>
La validazione client NON è sicurezza

È solo una cortesia per l'utente onesto: un attaccante può aggirarla in 10 secondi (disabilitando JS, modificando il DOM, inviando con curl). La validazione server è sempre necessaria. Tornerà nella sezione “Cenno al backend”.

Stilizzare i form

Gli elementi dei form si stilizzano con CSS come tutti gli altri, ma con qualche peculiarità.

Selettori utili

SelettoreDescrizione
input[type="text"]Tutti gli input testuali.
:focusElemento attualmente con il focus.
:hoverElemento sotto il cursore.
:disabledElementi disabilitati.
:requiredCampi obbligatori.
:valid / :invalidStato di validazione.
:user-valid / :user-invalidValidità solo dopo interazione utente (consigliato).
:placeholder-shownCampo che mostra il placeholder (vuoto).

Esempio completo

Form di contatto con stili moderni: bordi arrotondati, focus visibile, feedback rosso su :user-invalid, hover sul bottone.

Tre elementi ostili

<select>, checkbox/radio e <input type="file"> sono notoriamente difficili da stilizzare in modo uniforme su tutti i browser: la freccia del select, il pallino del radio e il pulsante “Sfoglia” sono grafiche di sistema. Per progetti professionali si nascondono gli elementi nativi e si ricreano con CSS o librerie dedicate.

Invio dei dati

Cosa succede quando l'utente preme “Invia”? Il browser:

  1. raccoglie tutti i campi del form che hanno un attributo name;
  2. li impacchetta come coppie name=valore;
  3. li invia all'URL specificato in action, con il metodo HTTP definito in method;
  4. attende la risposta del server (a meno di intercettazione via JavaScript).

L'attributo name: il fondamento

Regola d'oro: solo i campi con name vengono inviati al server. Un campo senza name esiste visivamente, ma è invisibile ai dati trasmessi. Premi “Invia” nel demo qui sotto per vedere quali campi finiscono nella query string:

No name, no party

Un campo senza name è invisibile per il server. Errore classico: si dimentica name e ci si chiede perché il dato non arriva.

L'attributo action: dove vanno i dati

html
<!-- URL relativo: stesso dominio del sito -->
<form action="/registrazione" method="post">

<!-- URL assoluto: dominio diverso -->
<form action="https://api.esempio.ch/contatti" method="post">

<!-- Action vuoto: stessa pagina (default) -->
<form action="" method="post">

L'attributo method: GET vs POST

GET — dati nell'URL
html
<form action="/cerca" method="get">
  <input type="text" name="q" value="formaggio">
  <input type="text" name="cat" value="alimentari">
  <button type="submit">Cerca</button>
</form>

<!-- URL risultante -->
<!-- /cerca?q=formaggio&cat=alimentari -->
POST — dati nel body
html
<form action="/login" method="post">
  <input type="text" name="user">
  <input type="password" name="pwd">
  <button type="submit">Login</button>
</form>

<!-- URL: /login   (i dati sono nascosti nel body) -->
CaratteristicaGETPOST
Visibilità datiNell'URLNel body
Salvabile nei preferitiNo
Modifica dati sul serverNo (lettura)Sì (scrittura)
Adatto a passwordNo
Adatto a fileNo
Caso d'uso tipicoRicerche, filtriLogin, registrazione, invio
Regola pratica

GET per leggere/cercare (i parametri sono visibili e linkabili). POST per inviare/modificare (password, dati sensibili, file).

L'attributo enctype

Specifica come codificare i dati prima di trasmetterli. Rilevante solo con method="post".

ValoreQuando usarlo
application/x-www-form-urlencodedDefault. Adatto alla maggior parte dei form.
multipart/form-dataObbligatorio quando il form contiene <input type="file">.
text/plainSolo per debug, non in produzione.
html
<!-- Form di upload: serve enctype="multipart/form-data" -->
<form action="/carica" method="post" enctype="multipart/form-data">
  <input type="file" name="documento">
  <button type="submit">Carica</button>
</form>

Cenno al backend

Il form HTML è solo metà del lavoro. L'altra metà è sul server: senza un endpoint che “ascolti”, il form non serve a nulla.

text
[Browser/Form]  --POST-->  [Server: PHP, Node.js, Python, ...]  -->  [Database / Email / ...]
                                                                  -->  Risposta al browser

Quando il browser invia un form, il programma server tipicamente:

  1. riceve la richiesta HTTP con i dati;
  2. estrae i campi cercandoli per name;
  3. li valida nuovamente (mai fidarsi del client!);
  4. li elabora: salva in database, invia un'email, autentica un utente, ecc.;
  5. risponde al browser con una nuova pagina, un redirect o un messaggio.
Validazione lato server: SEMPRE

La validazione HTML5 è una comodità per l'utente, NON una misura di sicurezza. Un utente malintenzionato può aggirarla in pochi secondi (disabilitando JavaScript, modificando l'HTML con i devtools, o inviando richieste con curl/Postman). Ogni validazione fatta nel form va sempre ripetuta sul server: solo lì può davvero proteggere i dati e l'applicazione.

La realizzazione concreta del backend viene affrontata in moduli successivi del corso, dove vedremo come scrivere endpoint che ricevono ed elaborano i form qui costruiti.

Esercizi

✏️Metti in pratica

Esercizio A — Base

Form di contatto

Costruisci un form con:

  • Nome (type="text", required)
  • Email (type="email", required)
  • Messaggio (<textarea>, maxlength="500")
  • Pulsante invia (type="submit")
  • Tutte le label associate correttamente con for/id

Risultato atteso: il browser blocca l'invio se i campi obbligatori sono vuoti o se l'email è malformata.

Esercizio B — Intermedio

Registrazione utente

Costruisci un form di registrazione con tre <fieldset>:

  • Dati personali: nome, cognome, data di nascita (con min/max per limitare gli anni)
  • Account: email, password (minlength="8"), conferma password
  • Preferenze: <select> con paese, checkbox newsletter, radio per genere
  • Pulsanti: Registrati (type="submit") e Annulla (type="reset")

Risultato atteso: form completo con tutti gli attributi di validazione, organizzato in fieldset visivamente distinti.

Esercizio C — Avanzato

Iscrizione SSSE/SIG con pattern

Costruisci un form “Iscrizione SSSE/SIG” che includa:

  • Nome (testo, required, minlength="2")
  • Cognome (testo, required, minlength="2")
  • Email (type="email", required)
  • CAP svizzero (pattern="[0-9]{4}" + title esplicativo)
  • Telefono CH (pattern per il formato +41 XX XXX XX XX)
  • Anno di nascita (type="number", min="1950", max="2026")

Risultato atteso: form con messaggi di errore configurati tramite title; gli stili :user-invalid colorano in rosso i campi non validi.