La maggior parte dei form HTML che incontro in produzione usa tre tipi di input: text, password e submit. Tutto qui. Eppure HTML ha avuto tipi di input avanzati, validazione con vincoli e funzionalità di accessibilità già da anni — tutto disponibile senza JavaScript e senza npm install. Usiamoli.
Tipi di Input che Vale la Pena Conoscere
Ogni tipo di input fa un lavoro concreto: attiva la tastiera mobile giusta, fornisce validazione integrata e comunica l'intento alle tecnologie assistive. Ecco quelli che compaiono costantemente nei progetti reali:
<!-- Email: validates format, shows email keyboard on mobile -->
<input type="email" name="email" autocomplete="email">
<!-- Phone: shows numeric keypad on mobile -->
<input type="tel" name="phone" autocomplete="tel" pattern="[0-9]{10,15}">
<!-- Number: spin buttons, min/max/step validation -->
<input type="number" name="quantity" min="1" max="99" step="1" value="1">
<!-- Date: native date picker (no library needed) -->
<input type="date" name="birthdate" min="1900-01-01" max="2026-12-31">
<!-- Range: slider with min/max/step -->
<input type="range" name="volume" min="0" max="100" step="5" value="50">
<!-- Color: native color picker -->
<input type="color" name="theme_color" value="#0066cc">
<!-- File: single or multiple file upload -->
<input type="file" name="resume" accept=".pdf,.doc,.docx">
<input type="file" name="photos" accept="image/*" multiple>type="date" restituisce il valore nel formato YYYY-MM-DD indipendentemente dalla lingua dell'utente — esattamente quello che vuoi quando invii i dati al server. Evita di analizzare le date da input di tipo type="text" quando puoi.Associazione delle Etichette — Falla Bene
Le etichette non sono una decorazione opzionale. Sono il modo principale con cui i lettori di schermo identificano gli input, e fare clic su un'etichetta dovrebbe portare il focus all'input associato. Esistono due approcci validi:
<!-- Method 1: for/id association (most common, most flexible) -->
<label for="email">Email address</label>
<input type="email" id="email" name="email">
<!-- Method 2: wrapping label (no id needed) -->
<label>
Email address
<input type="email" name="email">
</label>
<!-- Wrong: placeholder is NOT a label -->
<input type="email" name="email" placeholder="Email address">
<!-- placeholder disappears when the user starts typing — terrible UX for screen readers -->L'approccio for/id è generalmente preferibile perché mantiene etichette e input separati nel DOM, il che è più facile da stilizzare. Non affidarti mai al placeholder come sostituto dell'etichetta — mette in difficoltà gli utenti con disabilità cognitive e sparisce nel momento in cui si inizia a digitare. Il tutorial sulle etichette del W3C WAI e la guida WebAIM sui controlli dei form coprono entrambi le motivazioni di accessibilità in maggior dettaglio.
Validazione con Vincoli Integrata
La validazione con vincoli integrata di HTML viene eseguita prima dell'invio del form ed è genuinamente potente. Ottieni molti comportamenti utili gratuitamente:
<form>
<!-- required: field must have a value -->
<label for="fullname">Full name</label>
<input type="text" id="fullname" name="fullname" required>
<!-- minlength/maxlength: character count constraints -->
<label for="username">Username (3–20 chars)</label>
<input type="text" id="username" name="username"
minlength="3" maxlength="20" required>
<!-- pattern: regex-based validation -->
<label for="postcode">UK Postcode</label>
<input type="text" id="postcode" name="postcode"
pattern="[A-Z]{1,2}[0-9][0-9A-Z]?s?[0-9][A-Z]{2}"
title="Enter a valid UK postcode (e.g. SW1A 1AA)"
required>
<!-- min/max on date -->
<label for="checkin">Check-in date</label>
<input type="date" id="checkin" name="checkin"
min="2026-04-16" required>
<button type="submit">Submit</button>
</form>required. Il campo deve avere un valore. Per i checkbox, deve essere selezionato.pattern. Una regex a cui il valore deve corrispondere. Aggiungi sempre un attributotitle— diventa il testo del tooltip di validazione e fornisce all'utente un suggerimento sul formato atteso.minlength/maxlength. Conteggio dei caratteri per gli input di testo.maxlengthtronca silenziosamente l'input;minlengthvalida solo al momento dell'invio.min/max. Limiti numerici o di data. Funziona su inputnumber,date,rangeetime.step. Definisce gli incrementi validi.step="0.01"su un campo valuta consente i centesimi.step="any"disabilita completamente il controllo dello step.
fieldset e legend per il Raggruppamento
Quando hai un gruppo di input correlati — specialmente pulsanti radio o checkbox — <fieldset> e <legend> li raggruppano semanticamente. I lettori di schermo leggono la legenda prima di ogni input del gruppo, così gli utenti sanno sempre a quale domanda risponde un pulsante radio.
<form>
<fieldset>
<legend>Preferred contact method</legend>
<label>
<input type="radio" name="contact" value="email" checked>
Email
</label>
<label>
<input type="radio" name="contact" value="phone">
Phone
</label>
<label>
<input type="radio" name="contact" value="post">
Post
</label>
</fieldset>
<fieldset>
<legend>Notification preferences</legend>
<label>
<input type="checkbox" name="notify_new_posts" value="1">
New articles
</label>
<label>
<input type="checkbox" name="notify_replies" value="1">
Replies to my comments
</label>
</fieldset>
</form>L'API di Validazione con Vincoli
La Constraint Validation API ti dà accesso JavaScript alla stessa logica di validazione usata nativamente dal browser. Questo ti permette di attivare la validazione programmaticamente e impostare messaggi di errore personalizzati senza bloccare il submit:
const usernameInput = document.getElementById('username');
// Check if a single field is valid
console.log(usernameInput.checkValidity()); // true or false
console.log(usernameInput.validity.tooShort); // true if below minlength
console.log(usernameInput.validity.patternMismatch); // true if pattern fails
// Set a custom error message (shows in the browser's native validation tooltip)
usernameInput.setCustomValidity('That username is already taken.');
usernameInput.reportValidity(); // triggers the tooltip immediately
// Clear a custom error (important — once set, it sticks until cleared)
usernameInput.setCustomValidity('');
// Validate on the fly as the user types (async example — username availability)
usernameInput.addEventListener('input', async () => {
const value = usernameInput.value;
if (value.length < 3) return; // let minlength handle this
const response = await fetch(`/api/check-username?q=${encodeURIComponent(value)}`);
const { available } = await response.json();
usernameInput.setCustomValidity(available ? '' : 'Username is already taken.');
});Nota il dettaglio critico: una volta chiamato setCustomValidity() con una stringa non vuota, il campo è permanentemente invalido finché non lo si pulisce chiamando setCustomValidity(''). Dimenticare il passo di pulizia è il bug più comune quando si usa questa API.
novalidate per la Validazione JS Personalizzata
Se stai costruendo una UI di validazione personalizzata con messaggi di errore stilizzati (non i tooltip nativi del browser), aggiungi novalidate al form. Questo disabilita la validazione nativa del browser mantenendo disponibile la Constraint Validation API per i tuoi controlli:
<form id="signup-form" novalidate>
<div class="field">
<label for="signup-email">Email</label>
<input type="email" id="signup-email" name="email" required>
<span class="error" aria-live="polite"></span>
</div>
<div class="field">
<label for="signup-password">Password</label>
<input type="password" id="signup-password" name="password" minlength="8" required>
<span class="error" aria-live="polite"></span>
</div>
<button type="submit">Create account</button>
</form>const form = document.getElementById('signup-form');
form.addEventListener('submit', (e) => {
e.preventDefault();
let isValid = true;
form.querySelectorAll('input').forEach((input) => {
const errorEl = input.nextElementSibling;
if (!input.checkValidity()) {
isValid = false;
errorEl.textContent = input.validationMessage;
input.setAttribute('aria-invalid', 'true');
} else {
errorEl.textContent = '';
input.removeAttribute('aria-invalid');
}
});
if (isValid) {
form.submit();
}
});autocomplete e aria-describedby
Due attributi che migliorano significativamente l'esperienza utente dei form e sono facili da trascurare:
<!-- autocomplete: tells browsers/password managers what the field is for -->
<input type="text" name="fname" autocomplete="given-name">
<input type="text" name="lname" autocomplete="family-name">
<input type="email" name="email" autocomplete="email">
<input type="tel" name="phone" autocomplete="tel">
<input type="password" name="password" autocomplete="current-password">
<input type="password" name="new_pass" autocomplete="new-password">
<input type="text" name="cc" autocomplete="cc-number">
<!-- aria-describedby: links an input to a hint or error message -->
<label for="pass">Password</label>
<input
type="password"
id="pass"
name="password"
minlength="8"
aria-describedby="pass-hint"
required
>
<span id="pass-hint">Must be at least 8 characters.</span>L'attributo autocomplete usa valori token standardizzati definiti dallo spec WHATWG. Usando il token corretto, browser e password manager possono compilare automaticamente i campi in modo affidabile. aria-describedby associa un suggerimento o messaggio di errore a un input — i lettori di schermo leggono la descrizione dopo l'etichetta del campo, rendendo i vincoli udibili prima ancora che l'utente inizi a digitare.
Un Form di Login Accessibile Completo
Ecco tutto combinato — un form di login che funziona per utenti da tastiera, utenti di lettori di schermo e utenti mobile, usando solo HTML e una piccola quantità di JavaScript:
<form id="login-form" novalidate>
<h2>Sign in</h2>
<div class="field">
<label for="login-email">Email address</label>
<input
type="email"
id="login-email"
name="email"
autocomplete="email"
aria-describedby="email-error"
required
>
<span id="email-error" class="error" aria-live="polite" role="alert"></span>
</div>
<div class="field">
<label for="login-password">Password</label>
<input
type="password"
id="login-password"
name="password"
autocomplete="current-password"
aria-describedby="password-error"
required
>
<span id="password-error" class="error" aria-live="polite" role="alert"></span>
<a href="/forgot-password">Forgot password?</a>
</div>
<label class="inline">
<input type="checkbox" name="remember" value="1">
Keep me signed in for 30 days
</label>
<button type="submit">Sign in</button>
</form>Punti chiave: aria-live="polite" sugli span degli errori fa sì che i lettori di schermo annuncino gli errori quando appaiono senza interrompere quello che l'utente sta sentendo in quel momento. role="alert" rafforza questo per i lettori di schermo più vecchi. I valori di autocomplete corrispondono a quelli attesi dai password manager, quindi il completamento automatico funziona correttamente al primo tentativo.
Strumenti Utili
Durante la costruzione e il test dei form HTML, HTML Validator individua errori strutturali come etichette mancanti e ID duplicati prima che li trovino gli utenti. Per la pulizia generale dell'HTML, HTML Formatter mantiene il markup leggibile. Il riferimento MDN per l'elemento input è la documentazione più completa per tutti i tipi di input e i loro attributi.
Conclusione
I form HTML hanno molte più capacità integrate di quante ne utilizzi la maggior parte dei progetti. Scegliere il tipo di input giusto ti dà tastiere mobile, validazione e significato semantico gratuitamente. Un'associazione corretta delle etichette e aria-describedby rendono i form navigabili senza mouse. La Constraint Validation API ti fornisce hook JavaScript nella validazione nativa senza combattere con il browser. Metti insieme questi elementi e otterrai form che funzionano per tutti — prima ancora di scrivere una singola riga di logica di validazione personalizzata.