La plupart des formulaires HTML que je rencontre en production utilisent trois types d'input : text, password et submit. C'est tout. Pourtant, HTML dispose depuis des années de types d'input riches, d'une validation par contraintes et de fonctionnalités d'accessibilité — tout cela disponible sans JavaScript et sans npm install. Il est temps de les utiliser.
Les types d'input qui valent la peine d'être connus
Chaque type d'input fait de vrais travaux pour vous : il active le bon clavier mobile, fournit une validation intégrée et communique l'intention aux technologies d'assistance. Voici ceux qui reviennent constamment dans les projets réels :
<!-- 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" retourne la valeur au format YYYY-MM-DD quel que soit la locale de l'utilisateur — ce qui est exactement ce dont vous avez besoin pour l'envoyer à un serveur. N'essayez jamais de parser des dates depuis des inputs type="text" si vous pouvez l'éviter.Association de labels — faites-le correctement
Les labels ne sont pas une décoration optionnelle. Ce sont le principal moyen par lequel les lecteurs d'écran identifient les champs, et cliquer sur un label doit mettre le focus sur l'input associé. Il existe deux approches valides :
<!-- 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'approche for/id est généralement préférable car elle découple les labels et les inputs dans le DOM, ce qui est plus facile à styler. Ne vous fiez jamais au placeholder comme substitut de label — il échoue les utilisateurs ayant des handicaps cognitifs et disparaît dès que quelqu'un commence à taper. Le tutoriel sur les labels du W3C WAI et le guide WebAIM sur les contrôles de formulaire couvrent plus en détail la justification en matière d'accessibilité.
Validation par contraintes intégrée
La validation par contraintes intégrée de HTML s'exécute avant la soumission du formulaire et est véritablement puissante. Vous obtenez beaucoup de comportements utiles gratuitement :
<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. Le champ doit avoir une valeur. Pour les cases à cocher, elle doit être cochée.pattern. Une regex à laquelle la valeur doit correspondre. Ajoutez toujours un attributtitle— il devient le texte de la bulle de validation et donne aux utilisateurs un indice sur le format attendu.minlength/maxlength. Nombre de caractères pour les inputs texte.maxlengthtronque silencieusement la saisie ;minlengthne valide qu'à la soumission.min/max. Limites numériques ou de date. Fonctionne sur les inputsnumber,date,rangeettime.step. Définit les incréments valides.step="0.01"sur un champ monétaire autorise les centimes.step="any"désactive entièrement la vérification de l'incrément.
fieldset et legend pour le regroupement
Lorsque vous avez un groupe d'inputs liés — surtout des boutons radio ou des cases à cocher — <fieldset> et <legend> les regroupent sémantiquement. Les lecteurs d'écran lisent la légende avant chaque input du groupe, afin que les utilisateurs sachent toujours à quelle question répond un bouton 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 de validation par contraintes
L'API de validation par contraintes vous donne un accès JavaScript à la même logique de validation que le navigateur utilise nativement. Cela vous permet de déclencher la validation programmatiquement et de définir des messages d'erreur personnalisés sans bloquer la soumission :
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.');
});Notez le détail critique : une fois que vous appelez setCustomValidity() avec une chaîne non vide, le champ est définitivement invalide jusqu'à ce que vous le vidiez en appelant setCustomValidity(''). Oublier cette étape de réinitialisation est le bug le plus courant lors de l'utilisation de cette API.
novalidate pour la validation JS personnalisée
Si vous construisez une UI de validation personnalisée avec des messages d'erreur stylisés (pas les bulles natives du navigateur), ajoutez novalidate au formulaire. Cela désactive la validation native du navigateur tout en gardant l'API de validation par contraintes disponible pour vos propres vérifications :
<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 et aria-describedby
Deux attributs qui améliorent significativement l'UX des formulaires et qu'on oublie facilement :
<!-- 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'attribut autocomplete utilise des valeurs de tokens standardisées définies par la spécification WHATWG. En utilisant le bon token, les navigateurs et les gestionnaires de mots de passe peuvent remplir automatiquement les champs de manière fiable. aria-describedby associe un indice ou un message d'erreur à un input — les lecteurs d'écran lisent la description après le label du champ, rendant les contraintes audibles avant même que l'utilisateur ne commence à taper.
Un formulaire de connexion accessible complet
Voici tout combiné — un formulaire de connexion qui fonctionne pour les utilisateurs clavier, les utilisateurs de lecteurs d'écran et les utilisateurs mobiles, en utilisant uniquement HTML et un petit peu de 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>Points clés : aria-live="polite" sur les spans d'erreur signifie que les lecteurs d'écran annonceront les erreurs quand elles apparaissent sans interrompre ce que l'utilisateur entend actuellement. role="alert" renforce cela pour les lecteurs d'écran plus anciens. Les valeurs autocomplete correspondent à ce qu'attendent les gestionnaires de mots de passe, donc le remplissage automatique fonctionne correctement dès le premier essai.
Outils utiles
Lors de la construction et du test de formulaires HTML, le Validateur HTML détecte les erreurs structurelles comme les labels manquants et les IDs dupliqués avant que les utilisateurs ne les rencontrent. Pour le nettoyage général du HTML, le Formateur HTML maintient votre balisage lisible. La référence MDN de l'élément input est la documentation la plus complète pour tous les types d'input et leurs attributs.
Conclusion
Les formulaires HTML ont bien plus de capacités intégrées que la plupart des projets n'en exploitent. Choisir le bon type d'input vous apporte gratuitement les claviers mobiles, la validation et la signification sémantique. Une association de labels correcte et aria-describedby rendent les formulaires navigables sans souris. L'API de validation par contraintes vous donne des hooks JavaScript dans la validation native sans combattre le navigateur. Assemblez ces éléments et vous obtenez des formulaires qui fonctionnent pour tout le monde — avant d'écrire une seule ligne de logique de validation personnalisée.