Guida interattiva ai
Costruire ponti interattivi tra utente e server
I moduli (form) HTML sono il meccanismo principale per raccogliere input dagli utenti. Sono ovunque nell'esperienza web quotidiana: login, registrazione, ricerca, e-commerce, sondaggi, commenti.
Trasformano la navigazione da passiva (leggere contenuti) ad attiva (interagire, inviare informazioni).
L'elemento cardine per la creazione di un modulo è il tag <form>. Questo elemento agisce da contenitore per tutti i controlli — campi di testo, pulsanti, checkbox, ecc. — che costituiscono un modulo specifico.
Un form HTML è composto da diversi "pezzi" che lavorano insieme. Ecco una mappa mentale per orientarvi:
In questa lezione esploreremo ognuno di questi pezzi in dettaglio, costruendo form sempre più completi.
<form>Il tag <form> è il contenitore per tutti i controlli di un modulo. Due attributi fondamentali:
action: l'URL di destinazione dei dati (dove inviarli). Se omesso, invia alla stessa pagina (sconsigliato).method: come inviare i dati. Due valori:
GET (default): dati visibili nell'URL. Come una cartolina: tutti possono leggere. Va bene per ricerche e filtri.POST: dati nel corpo della richiesta, non visibili nell'URL. Come una busta chiusa. Obbligatorio per dati sensibili.<form action="/registrazione" method="POST">
<!-- Qui andranno i controlli -->
</form>
Regola: è severamente vietato annidare un <form> dentro un altro <form>.
Premete "Invia" per vedere cosa riceve il server.
Se non specificate method, il browser usa GET di default. Ricordatevi di scrivere method="POST" quando servono dati non visibili nell'URL!
Aprite CodePen e scrivete un form da zero. Provate a inviarlo e osservate cosa succede!
<!-- Scrivete un form che invia i dati all'endpoint di test -->
<form action="https://guida-form.pages.dev/api/echo" method="GET" target="_blank">
<p>Il vostro nome: <input type="text" name="nome"></p>
<button type="submit">Invia</button>
</form>
method="GET" in method="POST". Inviate di nuovo. L'URL è diverso?<input type="text" name="cognome">) e inviate: cosa cambia nella risposta?name="nome" dall'input e inviate?Il controllo più comune è <input type="text">: un campo per testo a riga singola. È un tag "vuoto" (non ha tag di chiusura).
Ma un input da solo non basta: l'utente non sa cosa scriverci! Serve un'etichetta: il tag <label>.
for/id:id univoco all'input: <input id="nome_utente">for alla label con lo stesso valore: <label for="nome_utente">Benefici:
<label for="nome_utente">Nome Utente:</label>
<input type="text" id="nome_utente">
Cosa ci scrivo?
Click sulla label: non succede nulla
Click sulla label: il cursore va nel campo!
Ogni controllo interattivo in un form (tranne i pulsanti) deve avere una <label> associata con for/id. Non usate mai solo del testo vicino all'input senza collegarlo!
Tre concetti fondamentali per capire come i form inviano informazioni:
name — il badge identificativo per il serverL'attributo name identifica il dato per il server. Come un badge identificativo: senza badge, il server ignora il campo. Il name è l'etichetta per il server; il <label> è l'etichetta per l'utente.
value — il valore associatoPer i campi di testo, il value è ciò che l'utente digita. I dati viaggiano come coppie name=value (es. username=Mario).
placeholder — il testo fantasmaTesto suggerimento nel campo vuoto, come il testo grigio scritto a matita in un modulo cartaceo: scompare quando si inizia a scrivere. NON sostituisce la <label>.
<button type="submit">Il pulsante che avvia la raccolta e l'invio di tutte le coppie name=value.
<form action="/processa" method="POST">
<label for="user">Nome Utente:</label>
<input type="text" id="user" name="username" placeholder="mario_rossi">
<button type="submit">Invia</button>
</form>
<!-- Se l'utente scrive "Mario", il server riceve: username=Mario -->
| name | value | inviato? |
|---|
Ricordate: id collega la label all'input (per l'utente). name identifica il dato per il server. Sono due cose diverse e servono entrambe!
<button> nei form!Un <button> dentro un <form> senza attributo type viene trattato dal browser come type="submit" di default. Questo significa che cliccandolo si invia il form! Se volete un pulsante che non invii (es. per toggle o altre azioni), dovete specificare type="button".
Aprite CodePen e mettete insieme tutto quello che avete imparato fin qui: form, label, input, name, e submit.
<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<!-- 1. Aggiungete un campo "Nome" con label, id, e name -->
<!-- 2. Aggiungete un campo "Email" con label, id, e name -->
<!-- 3. Aggiungete il pulsante di invio -->
</form>
<label for="...">, <input id="..." name="...">, e un placeholdername su un campo? Appare nella risposta?method da POST a GET: vedete i dati nell'URL della risposta?Oltre a type="text", esistono tipi di input specializzati che il browser gestisce in modo diverso:
type="password":type="email":@, dominio, ecc.)@ e . subito accessibili<input type="password" id="pwd" name="password">
<input type="email" id="email" name="user_email">
Il tipo password protegge dagli sguardi, non dalla trasmissione. Per la sicurezza della trasmissione serve HTTPS (il lucchetto nel browser). Il tipo email offre una validazione base gratuita, molto utile!
<textarea>Per messaggi, commenti o descrizioni lunghe, <input> non basta: serve <textarea>.
<input>:<textarea>...</textarea>value)rows: numero di righe visibili (altezza iniziale)cols: numero di caratteri visibili (larghezza iniziale)resize)placeholder funziona anche qui<label for="messaggio">Il vostro messaggio:</label>
<textarea id="messaggio" name="user_message" rows="5" cols="40"
placeholder="Scrivete qui il messaggio..."></textarea>
Attenzione agli spazi! Se scrivete <textarea> testo </textarea> con spazi extra, quegli spazi appariranno nel campo. Il contenuto tra i tag è letterale.
Aprite CodePen e arricchite il vostro form con campi password, email e textarea.
<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<p>
<label for="nome">Nome:</label>
<input type="text" id="nome" name="username" placeholder="Mario Rossi">
</p>
<p>
<label for="email">Email:</label>
<!-- Cambiate il type qui sotto: provate "email" -->
<input type="text" id="email" name="user_email" placeholder="mario@example.com">
</p>
<p>
<label for="pwd">Password:</label>
<!-- Cambiate il type qui sotto: provate "password" -->
<input type="text" id="pwd" name="user_pwd">
</p>
<p>
<label for="msg">Messaggio:</label><br>
<textarea id="msg" name="user_message" rows="4" cols="40"></textarea>
</p>
<button type="submit">Invia</button>
</form>
type="email". Provate a scrivere "ciao" e inviare: cosa succede?type="password". I caratteri vengono mascherati?rows della textarea: l'altezza iniziale cambia?<fieldset> e <legend>Quando i form crescono, servono raggruppamenti logici. Come i cassetti di un armadietto: ogni cassetto contiene documenti correlati, con un'etichetta sul fronte.
<fieldset>: il cassetto. Raggruppa controlli correlati. Il browser disegna un bordo attorno.<legend>: l'etichetta del cassetto. Deve essere il primo elemento figlio dentro <fieldset>.Benefici:
<legend> prima di ogni campo nel gruppo<fieldset>
<legend>Dati Anagrafici</legend>
<p>
<label for="nome">Nome:</label>
<input type="text" id="nome" name="nome">
</p>
<p>
<label for="cognome">Cognome:</label>
<input type="text" id="cognome" name="cognome">
</p>
</fieldset>
Quando l'utente deve scegliere una sola opzione da un gruppo (esclusiva), si usa <input type="radio">. Come i canali di una vecchia TV: solo uno alla volta.
name: è il name condiviso che li raggruppavalue diverso: è il valore inviato al serverchecked pre-seleziona un'opzione (solo una per gruppo)<label> (con for/id)<fieldset>/<legend> per l'accessibilità<fieldset>
<legend>Dimensione Bevanda</legend>
<p>
<input type="radio" id="piccolo" name="dimensione" value="s">
<label for="piccolo">Piccolo</label>
</p>
<p>
<input type="radio" id="medio" name="dimensione" value="m">
<label for="medio">Medio</label>
</p>
<p>
<input type="radio" id="grande" name="dimensione" value="l" checked>
<label for="grande">Grande</label>
</p>
</fieldset>
<!-- Se scelgo "Medio", il server riceve: dimensione=m -->
| name | value | inviato? |
|---|
Quando l'utente può scegliere zero, una o più opzioni (non esclusive), si usa <input type="checkbox">. Come una lista della spesa: spunti quello che vuoi.
name:name, con [] alla fine → name="interessi[]"name diversivalue è fondamentale! Se manca, il browser invia un inutile "on". Specificatelo sempre.
Solo le checkbox selezionate vengono inviate. checked pre-seleziona (più di una possibile).
<!-- Stesso name per opzioni correlate -->
<fieldset>
<legend>Interessi</legend>
<input type="checkbox" id="sport" name="interessi[]" value="sport">
<label for="sport">Sport</label>
<input type="checkbox" id="musica" name="interessi[]" value="musica" checked>
<label for="musica">Musica</label>
</fieldset>
<!-- Name diversi per opzioni indipendenti -->
<input type="checkbox" id="terms" name="accetta_termini" value="si">
<label for="terms">Accetto i termini</label>
<!-- Senza value: il browser invia "on" -->
<input type="checkbox" id="test" name="test_senza_value">
<label for="test">Test senza value</label>
| name | value | inviato? |
|---|
Aprite CodePen e create una sezione sondaggio con radio button e checkbox.
<form action="https://guida-form.pages.dev/api/echo" method="GET" target="_blank">
<!-- Radio: scelta singola -->
<fieldset>
<legend>Qual è il vostro linguaggio preferito?</legend>
<p>
<input type="radio" id="html" name="linguaggio" value="html">
<label for="html">HTML</label>
</p>
<p>
<input type="radio" id="css" name="linguaggio" value="css">
<label for="css">CSS</label>
</p>
<!-- Aggiungete un'altra opzione "JavaScript" -->
</fieldset>
<!-- Checkbox: scelta multipla -->
<fieldset>
<legend>Quali strumenti usate? (più risposte)</legend>
<!-- Aggiungete 3 checkbox con name="strumenti[]" -->
<!-- Suggerimento: value="vscode", "figma", "github" -->
</fieldset>
<button type="submit">Invia Sondaggio</button>
</form>
linguaggio=valore, le checkbox come strumenti[]=valorename diversi ai radio? Si possono selezionare tutti?value da una checkbox: cosa appare nella risposta?checked a una delle opzioni radio e a due checkbox: sono già selezionate al caricamento?Create un form di registrazione completo che usa tutti gli elementi visti nella Parte 1.
name: il server deve poter distinguere ogni dato ricevuto. Per le opzioni dello stesso gruppo, come si indicano?<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<fieldset>
<legend>Dati Personali</legend>
<!-- Aggiungete: nome (text), email (email), password (password) -->
<!-- Ogni campo: label + input con id, name, placeholder -->
</fieldset>
<fieldset>
<legend>Preferenze</legend>
<!-- Aggiungete: 3 radio "Ruolo" con name="ruolo" -->
<!-- Aggiungete: 2 checkbox "Interessi" con name="interessi[]" -->
</fieldset>
<!-- Aggiungete il pulsante submit -->
</form>
<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<fieldset>
<legend>Dati Personali</legend>
<p>
<label for="reg-nome">Nome:</label>
<input type="text" id="reg-nome" name="nome" placeholder="Mario Rossi">
</p>
<p>
<label for="reg-email">Email:</label>
<input type="email" id="reg-email" name="email" placeholder="mario@example.com">
</p>
<p>
<label for="reg-pwd">Password:</label>
<input type="password" id="reg-pwd" name="password">
</p>
</fieldset>
<fieldset>
<legend>Preferenze</legend>
<p><strong>Ruolo:</strong></p>
<p>
<input type="radio" id="reg-designer" name="ruolo" value="designer">
<label for="reg-designer">Designer</label>
</p>
<p>
<input type="radio" id="reg-developer" name="ruolo" value="developer">
<label for="reg-developer">Developer</label>
</p>
<p>
<input type="radio" id="reg-entrambi" name="ruolo" value="entrambi">
<label for="reg-entrambi">Entrambi</label>
</p>
<p><strong>Interessi:</strong></p>
<p>
<input type="checkbox" id="reg-frontend" name="interessi[]" value="frontend">
<label for="reg-frontend">Frontend</label>
</p>
<p>
<input type="checkbox" id="reg-backend" name="interessi[]" value="backend">
<label for="reg-backend">Backend</label>
</p>
</fieldset>
<button type="submit">Registrati</button>
</form>
| Elemento / Attributo | Scopo |
|---|---|
<form> | Contenitore del modulo |
action | URL di destinazione dei dati |
method | Metodo di invio: GET (URL) o POST (corpo) |
<input type="text"> | Campo testo a riga singola |
<input type="password"> | Campo con testo mascherato |
<input type="email"> | Campo email con validazione base |
<textarea> | Campo testo multi-riga |
<label for="..."> | Etichetta collegata a un controllo (via id) |
name | Identificativo del dato per il server |
value | Valore del dato inviato |
placeholder | Suggerimento nel campo vuoto (non sostituisce label) |
<button type="submit"> | Pulsante per inviare il form |
<fieldset> + <legend> | Raggruppamento logico con titolo |
<input type="radio"> | Scelta singola (name condiviso, value unico) |
<input type="checkbox"> | Scelta multipla (value obbligatorio!) |
checked | Pre-selezione per radio/checkbox |
<select> e <option>Per scegliere da una lista predefinita di opzioni, si usa il menu a tendina (dropdown) con <select> e <option>.
<select>: il contenitore, con name (per l'invio) e id (per la label)<option>: ogni voce selezionabile. Il testo tra i tag è ciò che l'utente vede; l'attributo value è ciò che viene inviato al serverSpecificare sempre value: se manca, il browser invia il testo interno dell'opzione.
<label for="nazione">Nazione:</label>
<select id="nazione" name="user_nation">
<option value="it">Italia</option>
<option value="fr">Francia</option>
<option value="de">Germania</option>
</select>
Premete "Invia" per vedere quale valore viene inviato.
Attributi per controllare il comportamento delle opzioni:
selected: opzione predefinita al caricamento della paginadisabled: opzione visibile ma non selezionabile (appare grigia)Pattern segnaposto (best practice):
<option value="" disabled selected>-- Selezionate un paese --</option>
Con value="" + disabled + selected: appare come scelta iniziale, obbliga l'utente a scegliere attivamente. Se il <select> ha required, questa opzione non sarà considerata valida.
<label for="paese">Paese di Residenza:</label>
<select id="paese" name="user_country">
<option value="" disabled selected>-- Selezionate un paese --</option>
<option value="it">Italia</option>
<option value="fr">Francia</option>
<option value="es" disabled>Spagna (Non disponibile)</option>
</select>
<optgroup> per raggruppare opzioni sotto un titolo non selezionabile:
label obbligatorio (il titolo del gruppo)<select id="bevanda" name="drink">
<optgroup label="Bevande Calde">
<option value="the">Tè</option>
<option value="caffe">Caffè</option>
</optgroup>
<optgroup label="Bevande Fredde">
<option value="acqua">Acqua</option>
<option value="succo">Succo</option>
</optgroup>
</select>
multiple per selezione multipla:
size suggerisce quante righe mostrare<select id="ingredienti" name="pizza[]" multiple size="5">
<option value="pomodoro">Pomodoro</option>
<option value="mozzarella">Mozzarella</option>
<option value="basilico">Basilico</option>
<option value="funghi">Funghi</option>
<option value="prosciutto">Prosciutto</option>
</select>
Aprite CodePen e create select con optgroup e provate la selezione multipla.
<form action="https://guida-form.pages.dev/api/echo" method="GET" target="_blank">
<p>
<label for="orario">Orario Preferito:</label>
<select id="orario" name="pref_hour">
<option value="" disabled selected>-- Selezionate --</option>
<!-- Aggiungete optgroup "Mattina" con opzioni 9:00, 10:00, 11:00 -->
<!-- Aggiungete optgroup "Pomeriggio" con opzioni 14:00, 15:00, 16:00 -->
</select>
</p>
<button type="submit">Invia</button>
</form>
value significativo (es. "09:00")multiple al select: come cambia l'aspetto?multiple, provate a selezionare più voci (Ctrl/Cmd+Click). Inviate: appaiono tutti i valori?size="4" con multiple: quante righe vedete?<input type="number"> per valori numerici:
min, max, step, value, placeholderstepstep="1": accetta solo numeri interi. Provare a scrivere "3.5" dà errore!step="0.01": accetta centesimi (utile per prezzi)step="0.1": accetta un decimalestep="any": qualsiasi decimale senza limiti di precisione<input type="number" name="qty" min="1" max="10" step="1" value="1">
<input type="number" name="price" min="0" step="0.01" placeholder="Es. 19.99">
<input type="number" name="measure" step="any">
<output><input type="range"> per selezione approssimativa con cursore:
min, max, step<output> risolve: elemento semantico per mostrare risultati. Si collega con for (punta all'id del range).
Con solo HTML, il valore non si aggiorna muovendo lo slider. Serve JavaScript.
<label for="soddisfazione">Soddisfazione (1-5):</label>
<input type="range" id="soddisfazione" name="satisfaction"
min="1" max="5" step="1" value="3">
<output for="soddisfazione">3</output>
Provate a muovere lo slider in modalità "Senza JS": l'output resta fisso! Poi attivate "Con JS" per vedere la differenza. Questo è un caso dove JavaScript fa una vera differenza nell'usabilità.
Aprite CodePen e provate i controlli numerici.
<form action="https://guida-form.pages.dev/api/echo" method="GET" target="_blank">
<p>
<label for="qty">Quantità (1-10):</label>
<input type="number" id="qty" name="quantita" min="1" max="10" step="1" value="1">
</p>
<p>
<label for="budget">Budget:</label>
<input type="range" id="budget" name="budget" min="0" max="1000" step="50" value="500">
<output for="budget">500</output>
</p>
<button type="submit">Invia</button>
</form>
step="1" in step="any" e riprovate con un decimalestep="50" del range in step="100": lo slider "salta" diversamente?Gestire date e orari è complicato: 10/10, Oct 10, 10 Ottobre... HTML semplifica con input specializzati che attivano selettori nativi e inviano formati standardizzati.
| Tipo | Seleziona | Formato inviato |
|---|---|---|
date | Anno, Mese, Giorno | AAAA-MM-GG |
time | Ora | HH:MM |
datetime-local | Data + Ora | AAAA-MM-GGTHH:MM |
month | Anno, Mese | AAAA-MM |
week | Anno, Settimana | AAAA-WNN |
Vincoli con min, max, step:
min/max: limite date/ore selezionabili (formato coerente col tipo)step per time: in secondi (900 = 15 min, 1800 = 30 min)step per date: in giorni (7 = stesso giorno della settimana)<input type="date" name="event_date">
<input type="time" name="start_time" min="07:00" max="18:00" step="1800">
<input type="datetime-local" name="arrival"
min="2026-01-01T00:00" max="2026-12-31T23:59">
Premete "Invia" per vedere i formati standardizzati.
| Tipo | Vantaggi | Validazione? |
|---|---|---|
search | Semantica corretta, possibile X per cancellare, ricerche recenti | No |
tel | Tastiera telefonica su mobile | No! Accetta qualsiasi testo |
url | Tastiera con / e . su mobile | Sì: richiede schema (http/https) |
<input type="search" name="query" placeholder="Parola chiave...">
<input type="tel" name="phone" placeholder="+39 123 456 7890">
<input type="url" name="website" placeholder="https://esempio.com">
type="search"Possibile icona X per cancellare
type="tel"Scrivete "ciao": nessun errore!
type="url"type="tel" NON valida il formato! Per controllare che sia un numero di telefono valido, servono pattern o controlli server-side.
type="color": apre il selettore colori nativo del sistema operativo. Il valore è sempre una stringa esadecimale #rrggbb minuscola.
<input type="color" name="fav_color" value="#0000ff">
type="hidden": campo completamente invisibile all'utente, ma partecipa all'invio. Deve avere name e value nel codice. Non necessita <label>.
Casi d'uso: ID utente, token di sicurezza, ID prodotto, timestamp, versione del form.
<input type="hidden" name="user_id" value="12345">
<input type="hidden" name="csrf_token" value="aBcDeF">
| name | value | inviato? |
|---|
<datalist><datalist> offre suggerimenti di autocompletamento senza forzare la scelta.
<datalist> con id e <option> con i suggerimenti<input> con l'attributo list (punta all'id del datalist)Funziona con: text, search, url, tel, email. Supporto variabile per date, time, number, range, color.
<input type="text" id="browser" name="user_browser" list="browsers_list">
<datalist id="browsers_list">
<option value="Chrome">
<option value="Firefox">
<option value="Safari">
<option value="Edge">
<option value="Opera">
</datalist>
Aprite CodePen e provate i controlli avanzati: date, colore, datalist e campo nascosto.
<form action="https://guida-form.pages.dev/api/echo" method="GET" target="_blank">
<p>
<label for="data">Data Evento:</label>
<input type="date" id="data" name="event_date">
</p>
<p>
<label for="colore">Colore Tema:</label>
<input type="color" id="colore" name="theme_color" value="#3b82f6">
</p>
<p>
<label for="citta">Città:</label>
<input type="text" id="citta" name="city" list="city_suggestions">
<datalist id="city_suggestions">
<option value="Milano">
<option value="Roma">
<option value="Napoli">
<option value="Torino">
<option value="Firenze">
</datalist>
</p>
<input type="hidden" name="form_version" value="2.0">
<button type="submit">Invia</button>
</form>
form_version=2.0?min al campo data (es. min="2026-01-01"): potete selezionare date precedenti?Create un form di prenotazione che combina i controlli della Parte 2: select con optgroup, date, number, range, datalist, e hidden.
<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<p>
<label for="pren-camera">Tipo Camera:</label>
<select id="pren-camera" name="room_type">
<!-- Segnaposto + optgroup Standard + optgroup Premium -->
</select>
</p>
<p>
<label for="pren-checkin">Check-in:</label>
<input type="date" id="pren-checkin" name="checkin">
<label for="pren-checkout">Check-out:</label>
<input type="date" id="pren-checkout" name="checkout">
</p>
<p>
<label for="pren-ospiti">Ospiti:</label>
<!-- Completate questo input: name="guests" -->
<input id="pren-ospiti">
</p>
<p>
<label for="pren-budget">Budget:</label>
<!-- Completate questo input: name="budget" -->
<input id="pren-budget">
</p>
<p>
<label for="pren-citta">Città:</label>
<!-- Completate questo input: name="city" -->
<input id="pren-citta">
</p>
<!-- Aggiungete un campo: name="offerta_id" -->
<button type="submit">Prenota</button>
</form>
<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<p>
<label for="pren-camera">Tipo Camera:</label>
<select id="pren-camera" name="room_type">
<option value="" disabled selected>-- Selezionate --</option>
<optgroup label="Standard">
<option value="single">Singola</option>
<option value="double">Doppia</option>
</optgroup>
<optgroup label="Premium">
<option value="suite">Suite</option>
<option value="deluxe">Deluxe</option>
</optgroup>
</select>
</p>
<p>
<label for="pren-checkin">Check-in:</label>
<input type="date" id="pren-checkin" name="checkin">
<label for="pren-checkout">Check-out:</label>
<input type="date" id="pren-checkout" name="checkout">
</p>
<p>
<label for="pren-ospiti">Ospiti:</label>
<input type="number" id="pren-ospiti" name="guests" min="1" max="6" step="1" value="1">
</p>
<p>
<label for="pren-budget">Budget (€):</label>
<input type="range" id="pren-budget" name="budget" min="50" max="500" step="50" value="200">
<output for="pren-budget">200</output>
</p>
<p>
<label for="pren-citta">Città:</label>
<input type="text" id="pren-citta" name="city" list="citta-list">
<datalist id="citta-list">
<option value="Milano">
<option value="Roma">
<option value="Firenze">
<option value="Venezia">
</datalist>
</p>
<input type="hidden" name="offerta_id" value="SUMMER2026">
<button type="submit">Prenota</button>
</form>
| Elemento / Attributo | Scopo |
|---|---|
<select> + <option> | Menu a tendina (dropdown) |
value su option | Valore inviato al server (diverso dal testo visibile) |
selected | Opzione predefinita |
disabled | Opzione non selezionabile |
<optgroup label="..."> | Raggruppa opzioni sotto un titolo |
multiple + size | Selezione multipla con lista visibile |
<input type="number"> | Campo numerico con spinner |
min, max, step | Vincoli su valori numerici e date |
step="any" | Accetta qualsiasi decimale |
<input type="range"> | Slider per selezione approssimativa |
<output for="..."> | Mostra valore (si aggiorna con JS) |
type="date", time, datetime-local | Selettori data/ora nativi |
type="search", tel, url | Input semantici (tel non valida!) |
type="color" | Selettore colore (#rrggbb) |
type="hidden" | Campo invisibile (dati tecnici) |
<datalist> + list | Suggerimenti autocompletamento |
HTML integra meccanismi di validazione eseguiti direttamente dal browser (lato client), senza JavaScript. Lo scopo principale è migliorare l'esperienza utente, fornendo feedback immediato su errori di compilazione.
"Client" = il computer/browser dell'utente (chi compila il form).
"Server" = il computer remoto che riceve i dati (chi li elabora).
requiredL'attributo required su <input>, <select> o <textarea> impedisce l'invio se il campo è vuoto.
Se l'utente prova a inviare:
Best practice: accompagnare i campi required con un indicatore visivo (es. *) nella label.
<label for="nome_req">Nome: <span style="color:red;">*</span></label>
<input type="text" id="nome_req" name="username" required>
Premete "Invia" per testare la validazione.
La validazione HTML (client-side) aiuta l'utente ma NON È SICUREZZA!
Può essere facilmente aggirata da chiunque.
Principio chiave: "Mai fidarsi ciecamente dell'input dell'utente!"
La validazione sul server (server-side) è SEMPRE necessaria! Il server che riceve i dati DEVE SEMPRE ri-validare ogni singolo dato. È l'unica vera garanzia di sicurezza e integrità.
Pensate alla validazione HTML come al buttafuori che controlla l'età all'ingresso di un locale. Utile, ma un documento falso può ingannarlo. La security interna (il server) deve ricontrollare.
Oltre a required, altri attributi di validazione client-side:
Lunghezza testo:
minlength: minimo caratteri richiestimaxlength: massimo caratteri consentiti (spesso blocca la digitazione)Valori numerici/date (già visti): min, max, step
Formato specifico con pattern:
pattern="[0-9]{5}" = "5 cifre numeriche"title per spiegare il formato richiesto (appare nel messaggio di errore)<input type="password" name="pwd" required minlength="8">
<input type="text" name="zip" required pattern="[0-9]{5}"
title="Il CAP deve contenere 5 cifre.">
Premete "Invia" per testare la validazione.
Aprite CodePen e aggiungete attributi di validazione a un form esistente.
<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<p>I campi con * sono obbligatori.</p>
<p>
<label for="nome_v">Nome: *</label>
<input type="text" id="nome_v" name="nome">
<!-- Aggiungete: required -->
</p>
<p>
<label for="email_v">Email: *</label>
<input type="email" id="email_v" name="email">
<!-- Aggiungete: required -->
</p>
<p>
<label for="pwd_v">Password (min 8 caratteri): *</label>
<input type="password" id="pwd_v" name="password">
<!-- Aggiungete: required, minlength="8" -->
</p>
<p>
<label for="cap_v">CAP (5 cifre):</label>
<input type="text" id="cap_v" name="cap">
<!-- Aggiungete: pattern="[0-9]{5}" title="Inserite 5 cifre" -->
</p>
<button type="submit">Registrati</button>
</form>
required ai primi 3 campi. Provate a inviare vuoto: cosa succede?minlength="8" alla password. Scrivete "abc" e inviate: errore?pattern="[0-9]{5}" e title al CAP. Scrivete "abc" e inviate@: il tipo email la blocca?Create un semplice form di login con validazione HTML.
name e value per il server<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<p>I campi con * sono obbligatori.</p>
<!-- Aggiungete: campo email con label, required -->
<!-- Aggiungete: campo password con label, required, minlength="8" -->
<!-- Aggiungete: checkbox "Ricordami" con label -->
<!-- Aggiungete: pulsante submit "Accedi" -->
</form>
<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<p>I campi con * sono obbligatori.</p>
<p>
<label for="login-email">Email: *</label>
<input type="email" id="login-email" name="email" required
placeholder="mario@example.com">
</p>
<p>
<label for="login-pwd">Password (min 8 caratteri): *</label>
<input type="password" id="login-pwd" name="password" required minlength="8">
</p>
<p>
<input type="checkbox" id="login-remember" name="remember" value="yes">
<label for="login-remember">Ricordami</label>
</p>
<button type="submit">Accedi</button>
</form>
<input type="file"> renderizza un pulsante "Sfoglia" per caricare file.
accept suggerisce quali tipi di file mostrare nella finestra di selezione:
.jpg, .pngimage/* (qualsiasi immagine). I Tipi MIME sono etichette standard che identificano il tipo di un file su Internet.application/pdfAltri attributi:
multiple: selezionare più file contemporaneamenterequired: upload obbligatoriocapture: su mobile, suggerisce fotocamera (user per selfie, environment per posteriore)<input type="file" name="photo" accept=".jpg,.png,image/*">
<input type="file" name="docs[]" accept="application/pdf" multiple required>
Per l'upload di file servono obbligatoriamente due attributi sul <form>:
method="POST": i file sono dati binari, non possono viaggiare nell'URLenctype="multipart/form-data": dice al browser di usare un "pacco speciale" per spedire file e testo insieme. Il default (application/x-www-form-urlencoded) non funziona per i file.Analogia: spedire una lettera con una foto allegata richiede un pacco con scomparti separati — uno per la lettera (testo), uno per la foto (file).
Senza method="POST" e enctype="multipart/form-data", l'upload fallirà.
<!-- CORRETTO -->
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="documento">
<button type="submit">Carica</button>
</form>
<!-- SBAGLIATO: manca enctype! -->
<form action="/upload" method="POST">
<input type="file" name="documento">
<button type="submit">Carica (non funzionerà)</button>
</form>
enctypePremete "Carica" per vedere la risposta.
enctypePremete "Carica" per vedere la risposta.
Provate a caricare un file con entrambi i form: quello con enctype mostrerà i dettagli del file nella risposta, l'altro no.
Aprite CodePen e create un form con upload di file.
<!-- Notate: method="POST" e enctype="multipart/form-data" -->
<form action="https://guida-form.pages.dev/api/echo"
method="POST"
enctype="multipart/form-data"
target="_blank">
<p>
<label for="nome_up">Nome:</label>
<input type="text" id="nome_up" name="nome" required>
</p>
<p>
<label for="foto">Foto profilo (JPG/PNG):</label>
<input type="file" id="foto" name="avatar" accept=".jpg,.png">
</p>
<button type="submit">Carica</button>
</form>
enctype="multipart/form-data" dal form e inviate di nuovo: il file arriva ancora?multiple all'input file: potete selezionare più file?required all'input file: potete inviare senza file?accept in accept="application/pdf": cosa cambia nella finestra di selezione?Tre tipi di pulsante (attributo type):
submit (default dentro un form): invia il form e attiva la validazionereset: ripristina tutti i campi ai valori iniziali. Quasi sempre sconsigliato: un click accidentale cancella tutto senza conferma e senza possibilità di annullare. Accettabile solo in form brevi dove ricompilare costa poco (es. filtri di ricerca, calcolatori)button: neutro, non fa nulla di default. Serve per azioni JavaScript personalizzateNota: preferite <button> a <input type="submit"> — può contenere HTML ricco (icone, testo formattato), mentre <input> accetta solo testo semplice via value.
<form action="/api/echo" method="POST">
<label for="nome">Nome:</label>
<input type="text" id="nome" name="nome">
<button type="submit">Invia (submit)</button>
<button type="button" onclick="alert('Non invio nulla!')">Azione Custom (button)</button>
<button type="reset">Reset</button>
</form>
Premete "Invia" per vedere cosa riceve il server.
Notate: il pulsante Reset cancella il campo, il pulsante Button mostra un alert senza inviare, il pulsante Submit invia i dati. Ricordate: un <button> dentro un form senza type esplicito è submit di default — specificate sempre type="button" per azioni non-submit!
L'uso corretto degli elementi HTML semantici (<form>, <label>, <input>, <button>, <fieldset>, <legend>) è la fondazione dell'accessibilità.
Benefici automatici del browser (senza codice aggiuntivo):
Evitare "finti controlli" con <div>/<span> + JavaScript: richiedono di reimplementare manualmente tutta l'accessibilità, rendendo il codice complesso e fragile.
<!-- BUONO: semantico e accessibile -->
<form>
<label for="nome">Nome:</label>
<input type="text" id="nome" name="nome">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<button type="submit">Invia</button>
</form>
<!-- CATTIVO: finti controlli con div -->
<div class="fake-form">
<span>Nome:</span>
<div class="fake-input">Scrivi qui...</div>
<span>Email:</span>
<div class="fake-input">Scrivi qui...</div>
<div class="fake-button" onclick="...">Invia</div>
</div>
Provate a navigare entrambi i form usando SOLO il tasto Tab. Nel primo il focus si sposta tra i campi; nel secondo... non succede nulla.
<div>Nome:
Email:
Indicare campi obbligatori in modo accessibile:
* dentro la <label> (non fuori)<span aria-label="obbligatorio">*</span>Disabilitare la validazione automatica:
novalidate sul <form>: disabilita per tutto il form (utile se la validazione è gestita da JS)formnovalidate su un <button type="submit">: disabilita solo per quel pulsante (utile per "Salva Bozza")<form novalidate> <!-- Nessun messaggio automatico -->
<input type="email" required>
<button type="submit">Invia (validazione JS)</button>
<button type="submit" formnovalidate>Salva Bozza</button>
</form>
Nota: questi attributi disabilitano solo il feedback automatico del browser, non la possibilità di controllare la validità via CSS o JS.
Aprite CodePen e provate la navigazione da tastiera e il controllo della validazione.
<form action="https://guida-form.pages.dev/api/echo" method="POST" target="_blank">
<p>I campi con * sono obbligatori.</p>
<fieldset>
<legend>Contatto</legend>
<p>
<label for="nome_a">Nome: *</label>
<input type="text" id="nome_a" name="nome" required>
</p>
<p>
<label for="email_a">Email: *</label>
<input type="email" id="email_a" name="email" required>
</p>
<p>
<label for="msg_a">Messaggio:</label><br>
<textarea id="msg_a" name="messaggio" rows="3"></textarea>
</p>
</fieldset>
<button type="submit">Invia</button>
<button type="submit" formnovalidate>Salva Bozza</button>
</form>
formnovalidate)id dai campi e i for dalle label: il click sulla label funziona ancora?Create un form professionale che combina validazione, file upload, e accessibilità.
*) che nel codice: quale attributo?<form action="https://guida-form.pages.dev/api/echo"
method="POST"
enctype="multipart/form-data"
target="_blank">
<p>I campi con * sono obbligatori.</p>
<fieldset>
<legend>Dati Personali</legend>
<!-- Nome (text, required), Email (email, required), Telefono (tel) -->
</fieldset>
<fieldset>
<legend>Il Vostro Messaggio</legend>
<!-- Oggetto (text, required, maxlength), Messaggio (textarea, required, minlength), Allegato (file) -->
</fieldset>
<!-- Checkbox privacy (required) -->
<!-- Due pulsanti: Invia e Salva Bozza (formnovalidate) -->
</form>
<form action="https://guida-form.pages.dev/api/echo"
method="POST"
enctype="multipart/form-data"
target="_blank">
<p>I campi con * sono obbligatori.</p>
<fieldset>
<legend>Dati Personali</legend>
<p>
<label for="cont-nome">Nome: *</label>
<input type="text" id="cont-nome" name="nome" required placeholder="Mario Rossi">
</p>
<p>
<label for="cont-email">Email: *</label>
<input type="email" id="cont-email" name="email" required placeholder="mario@example.com">
</p>
<p>
<label for="cont-tel">Telefono:</label>
<input type="tel" id="cont-tel" name="telefono" placeholder="+39 123 456 7890">
</p>
</fieldset>
<fieldset>
<legend>Il Vostro Messaggio</legend>
<p>
<label for="cont-oggetto">Oggetto: *</label>
<input type="text" id="cont-oggetto" name="oggetto" required maxlength="100"
placeholder="Oggetto del messaggio">
</p>
<p>
<label for="cont-msg">Messaggio: *</label><br>
<textarea id="cont-msg" name="messaggio" required minlength="20" rows="5"
placeholder="Scrivete almeno 20 caratteri..."></textarea>
</p>
<p>
<label for="cont-file">Allegato (PDF, JPG, PNG):</label>
<input type="file" id="cont-file" name="allegato" accept=".pdf,.jpg,.png">
</p>
</fieldset>
<p>
<input type="checkbox" id="cont-privacy" name="privacy" value="accepted" required>
<label for="cont-privacy">Accetto la privacy policy *</label>
</p>
<button type="submit">Invia</button>
<button type="submit" formnovalidate>Salva Bozza</button>
</form>
| Elemento / Attributo | Scopo |
|---|---|
required | Campo obbligatorio (blocca invio se vuoto) |
minlength / maxlength | Limiti lunghezza testo |
pattern | Formato specifico con regex |
title | Messaggio errore personalizzato per pattern |
| Validazione client-side | Aiuta l'utente, NON è sicurezza |
| Validazione server-side | SEMPRE obbligatoria |
<input type="file"> | Upload di file |
accept | Suggerisce tipi di file (non è validazione!) |
multiple (su file) | Selezione multipla di file |
capture | Fotocamera su mobile |
enctype="multipart/form-data" | Obbligatorio per upload file |
<button type="submit"> | Invia il form (default in form) |
<button type="reset"> | Reset campi (evitare!) |
<button type="button"> | Pulsante neutro (per JS) |
novalidate (su form) | Disabilita validazione per tutto il form |
formnovalidate (su button) | Disabilita validazione per quel pulsante |