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>
<!-- Aggiungete: nome (text), email (email), password (password) -->
<p>
<label for="nome">Nome:</label>
<input type="text" id="nome" name="nome" placeholder="Mario Rossi">
</p>
<p>
<label for="email">Email:</label>
<input type="email" id="email" name="email" placeholder="mario@example.com">
</p>
<p>
<label for="password">Password:</label>
<input type="password" id="password" name="password">
</p>
<!-- Ogni campo: label + input con id, name, placeholder -->
</fieldset>
<fieldset>
<legend>Preferenze</legend>
<!-- Aggiungete: 3 radio "Ruolo" con name="ruolo" -->
<fieldset>
<legend>Ruolo</legend>
<p>
<input type="radio" id="designer" name="ruolo" value="designer">
<label for="designer">Designer</label>
</p>
<p>
<input type="radio" id="developer" name="ruolo" value="developer">
<label for="developer">Developer</label>
</p>
<p>
<input type="radio" id="entrambi" name="ruolo" value="entrambi">
<label for="entrambi">Entrambi</label>
</p>
</fieldset>
<!-- Aggiungete: 2 checkbox "Interessi" con name="interessi[]" -->
<fieldset>
<legend>Interessi</legend>
<p>
<input type="checkbox" id="frontend" name="interessi[]" value="frontend">
<label for="frontend">Frontend</label>
</p>
<p>
<input type="checkbox" id="backend" name="interessi[]" value="backend">
<label for="backend">Backend</label>
</p>
</fieldset>
</fieldset>
<!-- Aggiungete il pulsante submit -->
<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? |
|---|
Aprite CodePen e provate i controlli avanzati: date, colore 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>
<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 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>
<!-- 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>
<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) |
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?Personalizzare l'aspetto dei controlli mantenendo accessibilità e funzionalità
Se avete provato ad applicare CSS a un <input> o a un <select>, vi sarete accorti che non si comportano come un <div> o un <p>. I form element sono storicamente legati al sistema operativo: il browser delega il rendering di molti controlli al SO, che li disegna con il proprio aspetto nativo.
Lo stesso <input type="checkbox"> può apparire completamente diverso su Chrome, Safari, Firefox, e su Windows rispetto a macOS.
Non tutti i controlli sono ugualmente stilizzabili. Tre categorie:
<input type="text">, <textarea>, <button>, <label><input type="checkbox">, <input type="radio"><select>, <input type="date">, <input type="range">, <input type="file">Altro problema: i form element non ereditano font-family e font-size dal genitore come la maggior parte degli elementi HTML. Se impostate un font sulla pagina, i vostri <input> e <button> continueranno a usare il font di default del browser. Va corretto manualmente.
Nelle prossime slide vedrete come riprendere il controllo sull'aspetto dei form, partendo da un reset base fino ad arrivare a checkbox e select completamente personalizzati.
appearance e il Reset BaseIl primo strumento per riprendere il controllo è la proprietà CSS appearance. Impostando appearance: none, dite al browser: "Non disegnare questo controllo con lo stile nativo — lascia che sia io a decidere l'aspetto con il mio CSS".
Oltre ad appearance, serve un reset base che risolva i problemi di ereditarietà dei font e del dimensionamento:
/* Reset base per i form element */
button,
input,
select,
textarea {
font-family: inherit; /* Eredita il font dalla pagina */
font-size: 16px; /* Dimensione leggibile, evita zoom su mobile */
box-sizing: border-box; /* Padding e bordo inclusi nella larghezza */
}
La proprietà appearance: none si usa sui singoli controlli quando serve:
/* Rimuove lo stile nativo del browser */
input[type="checkbox"],
button {
-webkit-appearance: none; /* Per Safari */
appearance: none;
}
Il selettore input[type="checkbox"] è un selettore per attributo: seleziona tutti gli <input> il cui attributo type ha valore "checkbox". La sintassi è elemento[attributo="valore"].
Prima di procedere, tre concetti CSS che userete per la prima volta in questa parte. Li approfondirete in lezioni dedicate — qui serve solo riconoscere la sintassi.
Selettori che si attivano in base allo stato di un elemento. Ne conoscete già una: :hover. In questa parte incontrerete:
:focus — quando l'elemento ha il focus (click o Tab):checked — quando un checkbox o radio è selezionato:disabled — quando un elemento ha l'attributo disabled:valid, :invalid — quando il valore supera o meno la validazione HTML:hover — passaggio del mouse (la conoscete già)Sintassi: selettore:nome-pseudo-classe, es. input:focus.
Elementi "virtuali" generati dal CSS, non presenti nell'HTML. Permettono di aggiungere contenuto decorativo:
::before — elemento fittizio prima del contenuto::after — elemento fittizio dopo il contenuto::placeholder — stilizza il testo segnaposto degli inputSintassi con due punti doppi: selettore::nome-pseudo-elemento, es. input::placeholder. Nota: ::before e ::after richiedono sempre la proprietà content (anche vuota: content: "").
Già visto nella slide precedente: [type="checkbox"] seleziona in base a un attributo HTML. Lo userete combinato con pseudo-classi: input[type="checkbox"]:checked seleziona i checkbox selezionati.
Non preoccupatevi di memorizzare tutto ora — li riconoscerete man mano che li usiamo nelle slide seguenti.
Con il reset base applicato, potete personalizzare l'aspetto dei campi di testo. Le proprietà più utili:
border — bordo personalizzato al posto di quello nativopadding — spazio interno per rendere il testo più leggibileborder-radius — angoli arrotondatibackground-color — colore di sfondoPer le textarea, aggiungete resize: vertical — impedisce all'utente di allargare il campo orizzontalmente (rompe il layout), ma permette di allungarlo verticalmente.
/* Input di testo personalizzato */
.campo-custom {
font-size: 18px;
font-family: inherit;
padding: 12px 16px;
background-color: #f0f4ff;
border: 2px solid #6366f1;
border-radius: 12px;
}
/* Textarea: impedisce il resize orizzontale */
textarea.campo-custom {
resize: vertical;
}
<form>
<label for="nome-stile">Nome:</label>
<input type="text" id="nome-stile" name="nome" class="campo-custom">
<label for="msg-stile">Messaggio:</label>
<textarea id="msg-stile" name="messaggio" class="campo-custom" rows="4"></textarea>
</form>
:focus — Mai Rimuovere, Sempre MigliorareQuando un utente clicca su un campo o ci arriva premendo Tab, quell'elemento riceve il focus. Il browser lo segnala con un contorno (outline) — è così che gli utenti che navigano con la tastiera sanno dove si trovano.
Regola importante: Non scrivete mai outline: none senza fornire un'alternativa visiva. Togliere il focus ring senza sostituirlo è un errore grave di accessibilità — gli utenti che navigano con la tastiera non saprebbero più su quale campo si trovano.
La tecnica corretta: cambiate il colore del bordo, aggiungete un box-shadow come "anello" e impostate outline: 3px solid transparent (per la modalità Alto Contrasto di Windows, dove le ombre non sono visibili).
/* Stato focus: anello colorato + bordo evidenziato */
.campo-custom:focus {
border-color: #3730a3;
box-shadow: 0 0 0 3px rgba(99, 91, 255, 0.8);
outline: 3px solid transparent; /* Per Windows High Contrast */
}
<form>
<label for="email-focus">Email:</label>
<input type="email" id="email-focus" name="email" class="campo-custom"
placeholder="vostro@email.it">
</form>
Provate a cliccare sul campo e poi a usare Tab per spostarvi: vedrete l'anello di focus apparire e scomparire.
::placeholder, :disabled e [readonly]Lo pseudo-elemento ::placeholder permette di stilizzare il testo segnaposto — quel testo grigio che appare nei campi vuoti:
/* Stilizzare il placeholder */
.campo-custom::placeholder {
color: #8b8ec5;
font-style: italic;
}
Per i campi disabilitati, la pseudo-classe :disabled si attiva quando l'elemento ha l'attributo HTML disabled:
/* Stile per campi disabilitati */
.campo-custom:disabled {
background-color: #eee;
border-color: #ccc;
color: #999;
cursor: not-allowed;
}
Per i campi di sola lettura (readonly), il selettore per attributo [readonly]:
/* Stile per campi in sola lettura */
.campo-custom[readonly] {
background-color: #f9f9f9;
border-style: dashed;
color: #666;
}
La differenza tra disabled e readonly: un campo disabled non viene inviato con il form e non può ricevere il focus. Un campo readonly viene inviato ma l'utente non può modificarlo — utile per mostrare valori calcolati o pre-compilati.
<form>
<label for="email-ph">Email:</label>
<input type="email" id="email-ph" name="email" class="campo-custom"
placeholder="es. mario@email.it">
<label for="codice-ro">Codice ordine:</label>
<input type="text" id="codice-ro" name="codice" class="campo-custom"
value="ORD-2024-001" readonly>
<label for="campo-dis">Campo bloccato:</label>
<input type="text" id="campo-dis" name="bloccato" class="campo-custom"
value="Non modificabile" disabled>
</form>
I <button> nativi del browser hanno stili che variano molto tra i sistemi operativi. Per un aspetto uniforme e moderno, si parte da un reset e si ricostruisce lo stile.
/* Stile moderno per il bottone */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 20px;
min-height: 44px; /* Area di tocco accessibile (WCAG 2.1) */
background-color: #3e68ff;
color: #fff;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.18);
cursor: pointer;
}
/* Hover, Focus, Active, Disabled */
.btn:hover { background-color: #2952cc; }
.btn:focus {
outline-style: solid;
outline-color: transparent;
box-shadow: 0 0 0 4px #1a3399;
}
.btn:active {
transform: translateY(1px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
}
.btn:disabled {
background-color: #aaa;
cursor: not-allowed;
box-shadow: none;
}
Passate il mouse sul bottone, cliccateci, usate Tab per raggiungerlo — ogni stato ha un feedback visivo chiaro.
Stile variabile tra browser e SO
.btnStile uniforme e accessibile su tutti i browser
Su CodePen, costruite un'app per gestire una lista di note. Un form in alto per aggiungerne di nuove, e ogni nota ha il proprio form con un pulsante per eliminarla. Scrivete sia l'HTML sia il CSS seguendo i commenti nello scheletro di partenza.
Apri il mockup su Figma per vedere il risultato da replicare.
<!-- Titolo della pagina -->
<!-- Form per AGGIUNGERE una nota
method POST, action: https://guida-forms.pages.dev/api/notes
classe: ex-note-add-form -->
<!-- Campo hidden: name="action" value="add" -->
<!-- Campo hidden: name="note[]" value="Esempio nota"
(trasporta le note esistenti ad ogni invio) -->
<!-- Campo di testo: name="nota",
placeholder "Inserisci una nuova nota", obbligatorio -->
<!-- Bottone di invio: "Aggiungi Nota" -->
<!-- ...non vi starete scordando di chiudere il form, vero? -->
<!-- div con classe: ex-note-list -->
<!-- Form per ELIMINARE una nota
method POST, stessa action del form sopra
classe: ex-note-card -->
<!-- Campo hidden: name="action" value="delete" -->
<!-- Campo hidden: name="delete_index" value="0"
(indica quale nota eliminare) -->
<!-- Campo hidden: name="note[]" value="Esempio nota"
(stessa tecnica del form sopra) -->
<!-- Input di testo disabilitato con il testo della nota -->
<!-- Bottone di invio: "Elimina" -->
<!-- ...non vi starete scordando di chiudere il form, vero? -->
<!-- ...non vi starete scordando di chiudere il div, vero? -->
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, sans-serif;
background: #fff;
padding: 32px 24px;
}
<h1>Crea una Lista di Note</h1>
<form method="POST"
action="https://guida-forms.pages.dev/api/notes"
class="ex-note-add-form">
<input type="hidden" name="action" value="add">
<input type="hidden" name="note[]" value="Esempio nota">
<input type="text" name="nota"
placeholder="Inserisci una nuova nota" required>
<button type="submit">Aggiungi Nota</button>
</form>
<div class="ex-note-list">
<form method="POST"
action="https://guida-forms.pages.dev/api/notes"
class="ex-note-card">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="delete_index" value="0">
<input type="hidden" name="note[]" value="Esempio nota">
<input type="text" value="Esempio nota" disabled>
<button type="submit">Elimina</button>
</form>
</div>
/* Reset */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, sans-serif;
background: #fff;
padding: 32px 24px;
}
h1 {
text-align: center;
font-weight: 700;
margin-bottom: 24px;
}
/* Form di aggiunta */
.ex-note-add-form {
display: flex;
gap: 8px;
margin-bottom: 32px;
}
.ex-note-add-form input[type="text"] {
flex: 1;
padding: 10px 14px;
border: 1px solid #d4d4d4;
border-radius: 6px;
font-size: 1rem;
}
.ex-note-add-form button {
background: #16a34a;
color: white;
border: none;
border-radius: 6px;
padding: 10px 20px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
}
.ex-note-add-form button:hover {
background: #15803d;
}
/* Lista note */
.ex-note-list {
max-width: 500px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 10px;
}
/* Card nota */
.ex-note-card {
display: flex;
align-items: center;
padding: 8px 12px;
border: 1px solid #d4d4d4;
border-radius: 6px;
background: #f5f5f5;
gap: 8px;
}
.ex-note-card input[type="text"] {
flex: 1;
border: none;
background: transparent;
font-size: 1rem;
font-family: inherit;
color: #1a1a1a;
padding: 4px 6px;
}
.ex-note-card button {
background: #dc2626;
color: white;
border: none;
border-radius: 4px;
padding: 6px 14px;
font-weight: 600;
font-size: 0.85rem;
cursor: pointer;
}
.ex-note-card button:hover {
background: #b91c1c;
}
/* Reset */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, sans-serif;
background: #fff;
padding: 32px 24px;
}
h1 {
text-align: center;
font-weight: 700;
margin-bottom: 24px;
}
/* Form di aggiunta — griglia a 2 colonne */
.ex-note-add-form {
display: grid;
grid-template-columns: 1fr auto;
gap: 8px;
margin-bottom: 32px;
}
.ex-note-add-form input[type="text"] {
padding: 10px 14px;
border: 1px solid #d4d4d4;
border-radius: 6px;
font-size: 1rem;
}
.ex-note-add-form button {
background: #16a34a;
color: white;
border: none;
border-radius: 6px;
padding: 10px 20px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
}
.ex-note-add-form button:hover {
background: #15803d;
}
/* Lista note — griglia centrata a larghezza limitata */
.ex-note-list {
display: grid;
grid-template-columns: minmax(0, 500px);
justify-content: center;
gap: 10px;
}
/* Card nota — griglia a 2 colonne */
.ex-note-card {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
padding: 8px 12px;
border: 1px solid #d4d4d4;
border-radius: 6px;
background: #f5f5f5;
gap: 8px;
}
.ex-note-card input[type="text"] {
border: none;
background: transparent;
font-size: 1rem;
font-family: inherit;
color: #1a1a1a;
padding: 4px 6px;
}
.ex-note-card button {
background: #dc2626;
color: white;
border: none;
border-radius: 4px;
padding: 6px 14px;
font-weight: 600;
font-size: 0.85rem;
cursor: pointer;
}
.ex-note-card button:hover {
background: #b91c1c;
}
I checkbox e i radio button nativi presentano due problemi:
font-size non cambia la dimensione del checkbox nativo, che resta piccolo e fuori scala rispetto al design.La soluzione è un processo in 4 step:
appearance: none::before:focus, :disabled, :hover)Il risultato è un checkbox (o radio) completamente personalizzato, accessibile e coerente su tutti i browser.
Nella prossima slide vedrete tutti e 4 gli step in un'unica demo interattiva dove potete costruire il checkbox passo dopo passo.
Costruiamo un checkbox personalizzato step by step. Usate i pulsanti per aggiungere un layer di CSS alla volta e osservare il risultato.
/* Step 1: Rimuove lo stile nativo */
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
/* Step 2: Ricostruisce il box */
input[type="checkbox"] {
background-color: #fff;
width: 20px;
height: 20px;
border: 2px solid currentColor;
border-radius: 3px;
display: grid;
place-content: center;
}
/* Step 3: Spunta con ::before */
/* Qui sarebbe meglio usare background-image
o qualche tecnica più avanzata */
input[type="checkbox"]::before {
content: "✔";
color: #3e68ff;
font-size: 14px;
opacity: 0;
}
input[type="checkbox"]:checked::before {
opacity: 1;
}
/* Step 4: Stati */
input[type="checkbox"]:focus {
outline: 2px solid currentColor;
outline-offset: 2px;
}
input[type="checkbox"]:hover {
border-color: #3e68ff;
}
input[type="checkbox"]:disabled {
opacity: 0.6;
cursor: not-allowed;
}
Variante Radio: Stessa tecnica con due differenze — border-radius: 50% per il box circolare e un ::before circolare con background-color invece dell'emoji.
<select> — L'Elemento Più Difficile da StilizzareIl <select> è stato per anni l'elemento più frustrante per chi voleva personalizzare i form. Il motivo? La lista delle opzioni (il "dropdown") è gestita direttamente dal sistema operativo — il CSS non può raggiungerla. Per questo, molti sviluppatori hanno costruito select falsi con JavaScript, perdendo accessibilità e prestazioni.
Con il CSS classico, si poteva stilizzare solo il "bottone" esterno del select — quello che mostra l'opzione selezionata. La lista restava nativa, con l'aspetto del SO.
Vi mostriamo due approcci: quello tradizionale (funziona ovunque) e quello nuovo (sperimentale, Chrome 135+), che finalmente permette di personalizzare tutto.
Il <select> è l'elemento dei form più difficile da personalizzare. Il bottone si può stilare, ma il dropdown resta nativo del sistema operativo. La tecnica attuale si basa su tre passaggi:
appearance: none per rimuovere la freccia nativa<div> wrapper che fornisce bordo, sfondo e posiziona una freccia custom<select> e freccia ::after nella stessa areaselect {
appearance: none;
background-color: transparent;
border: none;
padding: 0 16px 0 0;
width: 100%;
font: inherit;
cursor: inherit;
outline: none;
}
.select-wrapper {
border: 2px solid #777;
border-radius: 4px;
padding: 8px 12px;
background-color: #fff;
display: grid;
grid-template-areas: "select";
align-items: center;
}
.select-wrapper select,
.select-wrapper::after {
grid-area: select;
}
/* Qui sarebbe meglio usare background-image
o un SVG. Per semplicità usiamo un'emoji. */
.select-wrapper::after {
content: "▼";
color: #777;
font-size: 14px;
justify-self: end;
}
<label for="paese">Paese:</label>
<div class="select-wrapper">
<select id="paese" name="paese">
<option value="" disabled selected>
-- Selezionate --
</option>
<option value="it">Italia</option>
<option value="fr">Francia</option>
<option value="de">Germania</option>
</select>
</div>
appearance: base-select — Chrome 135+ (aprile 2025) introduce una tecnologia sperimentale che permette di personalizzare tutto: bottone, dropdown, opzioni, freccia. Per ora funziona solo su Chrome e non è pronta per la produzione. Per approfondire: The <select> element can now be customized with CSS (Chrome DevRel).
Ricordate la validazione HTML vista nelle Parti precedenti? Con CSS potete dare feedback visivo immediato sulla natura dei campi.
Due pseudo-classi:
:required — si attiva su ogni campo che ha l'attributo required:optional — si attiva su ogni campo che non ha required/* Campi obbligatori: bordo pieno e più evidente */
input:required,
textarea:required,
select:required {
border: 2px solid #333;
}
/* Campi opzionali: bordo tratteggiato e più leggero */
input:optional,
textarea:optional,
select:optional {
border: 2px dashed #999;
}
<form>
<label for="nome-req">Nome: *</label>
<input type="text" id="nome-req" name="nome" required
class="campo-custom">
<label for="tel-opt">Telefono (opzionale):</label>
<input type="tel" id="tel-opt" name="telefono"
class="campo-custom">
<button type="submit" class="btn">Invia</button>
</form>
Un consiglio: non usate solo il colore per distinguere obbligatorio da opzionale — è problematico per utenti con daltonismo. Affiancate sempre un indicatore testuale, come l'asterisco * nella label o la scritta "(opzionale)".
:invalid vs :user-invalid — La Differenza che ContaLe pseudo-classi :valid e :invalid si attivano in base alla validazione HTML del campo. Per esempio, un <input type="email" required> è :invalid se è vuoto o contiene un testo che non è un indirizzo email.
C'è un grosso problema: :invalid si attiva subito al caricamento della pagina, prima che l'utente abbia toccato il form!
/* PROBLEMA: bordo rosso SUBITO, anche prima che l'utente scriva! */
input:invalid {
border: 2px solid red;
}
/* SOLUZIONE: bordo rosso solo DOPO che l'utente ha interagito */
input:user-invalid,
textarea:user-invalid {
border: 2px solid #d32f2f;
background-color: #fff5f5;
}
/* Bordo verde quando il valore è corretto, dopo l'interazione */
input:user-valid,
textarea:user-valid {
border: 2px solid #2e7d32;
background-color: #f5fff5;
}
:user-valid e :user-invalid sono supportate da tutti i browser moderni (Baseline 2023). Usate sempre :user-invalid al posto di :invalid per la validazione visiva.
:invalid (bordo rosso subito!)Il campo è rosso al caricamento, ancora prima che scriviate!
:user-invalid (dopo interazione)Rosso solo dopo che interagite e uscite dal campo con un valore non valido
accent-color — Il Colore del Brand in Una RigaPer un cambio di colore rapido, esiste una scorciatoia: la proprietà accent-color. Con una sola riga di CSS, colorate i controlli nativi del browser — checkbox, radio, range slider e progress bar:
/* Una riga per colorare checkbox, radio, range e progress */
:root {
accent-color: #3e68ff;
}
/* Oppure su singoli elementi */
input[type="checkbox"] {
accent-color: #3e68ff;
}
input[type="radio"] {
accent-color: hotpink;
}
Il browser sceglie automaticamente il colore giusto per il segno di spunta (bianco su sfondo scuro, scuro su sfondo chiaro) garantendo il contrasto.
Quando usare accent-color vs appearance: none? Se vi serve solo cambiare il colore dei controlli nativi, accent-color è perfetta — veloce, accessibile, nessun rischio. Se vi serve personalizzare la forma, le dimensioni o aggiungere animazioni, serve la tecnica completa con appearance: none + ::before.
field-sizing: content — Dimensioni AutomaticheAvete mai desiderato che una textarea crescesse automaticamente man mano che l'utente scrive? La proprietà field-sizing: content fa esattamente questo.
textarea {
field-sizing: content;
min-height: 56px; /* Almeno ~3 righe */
min-width: 200px;
max-width: 500px;
}
select {
field-sizing: content;
min-width: 80px;
}
input {
field-sizing: content;
min-width: 100px;
max-width: 400px;
}
Supporto browser: Chrome 123+, Edge 123+, Safari 26.2+. Firefox non lo supporta ancora. Usatelo come progressive enhancement — i browser che non lo supportano mostreranno i campi a dimensione fissa. Nessun danno.
| Proprietà / Tecnica | Scopo |
|---|---|
font-family: inherit | I form element ereditano il font della pagina |
box-sizing: border-box | Dimensioni prevedibili con padding e bordo inclusi |
appearance: none | Rimuove lo stile nativo del browser |
:focus + box-shadow | Focus ring accessibile e personalizzato |
::placeholder | Stilizzare il testo segnaposto |
:hover, :active | Feedback visivo al passaggio del mouse e al click |
:disabled / [readonly] | Stile per elementi disabilitati e di sola lettura |
appearance: none + ::before | Checkbox e radio completamente personalizzati |
clip-path | Creare forme geometriche (spunta, freccia) |
:checked | Stilizzare checkbox/radio quando selezionati |
appearance: base-select | Opt-in al customizable select (sperimentale) |
::picker(select), ::picker-icon | Personalizzare dropdown e freccia del select |
:required / :optional | Distinguere campi obbligatori da opzionali |
:user-valid / :user-invalid | Validazione visiva dopo interazione utente |
accent-color | Colorare checkbox/radio/range/progress con un colore |
field-sizing: content | Dimensioni automatiche basate sul contenuto |
Ricordate:
font: inherit, box-sizing):user-invalid al posto di :invalid per la validazione visivaaccent-color è la scorciatoia perfetta per un branding rapidoSu CodePen, trovate un form di registrazione con stili di default del browser. Include: campo nome, campo email obbligatorio, campo password, checkbox "Accetto i termini", select per il paese e bottone di invio. Trasformatelo in un form completamente stilizzato applicando tutte le tecniche viste in questa parte.
::before, stati.base-select con @supports?:invalid?<form action="https://guida-forms.pages.dev/api/echo"
method="POST" target="_blank">
<label for="ex3-nome">Nome:</label>
<input type="text" id="ex3-nome" name="nome">
<label for="ex3-email">Email: *</label>
<input type="email" id="ex3-email" name="email" required>
<label for="ex3-pwd">Password: *</label>
<input type="password" id="ex3-pwd" name="password"
required minlength="8">
<div class="inline-option">
<input type="checkbox" id="ex3-termini" name="termini"
value="accetto" required>
<label for="ex3-termini">Accetto i termini e condizioni</label>
</div>
<label for="ex3-paese">Paese:</label>
<div class="select-wrapper">
<select id="ex3-paese" name="paese">
<option value="" disabled selected>-- Selezionate --</option>
<option value="it">Italia</option>
<option value="fr">Francia</option>
<option value="de">Germania</option>
<option value="es">Spagna</option>
</select>
</div>
<button type="submit">Registrati</button>
</form>
/* Scrivete il vostro CSS qui */
/* 1. Reset base */
input, textarea, select, button {
font-family: inherit;
font-size: 16px;
box-sizing: border-box;
}
/* 2. Campi di testo */
input[type="text"],
input[type="email"],
input[type="password"] {
padding: 8px 12px;
border: 2px solid #8b8a8b;
border-radius: 4px;
width: 100%;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus {
border-color: #3730a3;
box-shadow: 0 0 0 3px rgba(99, 91, 255, 0.8);
outline: 3px solid transparent;
}
/* 3. Bottone */
button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 20px;
min-height: 44px;
background-color: #3e68ff;
color: #fff;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
}
button:hover { background-color: #2952cc; }
button:focus {
outline-style: solid;
outline-color: transparent;
box-shadow: 0 0 0 4px #1a3399;
}
/* 4. Checkbox personalizzato */
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
margin: 0;
background-color: #fff;
color: currentColor;
width: 20px;
height: 20px;
border: 2px solid currentColor;
border-radius: 3px;
display: grid;
place-content: center;
}
input[type="checkbox"]::before {
content: "";
width: 12px;
height: 12px;
transform: scale(0);
box-shadow: inset 16px 16px #3e68ff;
clip-path: polygon(14% 44%, 0 65%, 50% 100%,
100% 16%, 80% 0%, 43% 62%);
}
input[type="checkbox"]:checked::before {
transform: scale(1);
}
/* 5. Select con wrapper */
select {
appearance: none;
background-color: transparent;
border: none;
width: 100%;
font-family: inherit;
font-size: inherit;
cursor: pointer;
outline: none;
}
.select-wrapper {
border: 2px solid #777;
border-radius: 4px;
padding: 8px 12px;
background-color: #fff;
display: grid;
grid-template-areas: "select";
align-items: center;
}
.select-wrapper select,
.select-wrapper::after {
grid-area: select;
}
/* Per semplicità usiamo un'emoji come freccia.
In produzione: background-image o SVG. */
.select-wrapper::after {
content: "▼";
color: #777;
font-size: 14px;
justify-self: end;
}
/* 6. Validazione visiva */
input:user-invalid {
border: 2px solid #d32f2f;
background-color: #fff5f5;
}
input:user-valid {
border: 2px solid #2e7d32;
background-color: #f5fff5;
}