Formulaires · Démo interactive

Les formulaires
que le navigateur gère seul

Avant de dégainer une librairie de validation, le HTML natif fait déjà énormément : contraintes, types riches, autocomplétion, messages sur mesure. Six leviers, zéro dépendance.

01

Validation native — Constraint API

required, type="email", minlength, pattern : le navigateur valide sans une ligne de JS, et les pseudo-classes :valid / :invalid stylent le champ. Essaie une adresse incomplète.

<form id="form" novalidate>
  <label for="email">E-mail (requis)</label>
  <input type="email" id="email" required minlength="5" placeholder="prenom@exemple.be">
  <button type="submit">Vérifier</button>
  <p id="msg" role="status" aria-live="polite"></p>
</form>
input {
  padding: .5rem; color: #c9d4e8;
  background: #0d1117; border: 1px solid #1e2d44; border-radius: 6px;
}
/* le navigateur reflète l'état via ces pseudo-classes */
input:invalid { border-color: #f87171; }
input:valid   { border-color: #4ade80; }
const form  = document.getElementById('form');
const email = document.getElementById('email');
const msg   = document.getElementById('msg');

form.addEventListener('submit', (e) => {
  e.preventDefault();
  // checkValidity() + validationMessage : tout est natif, zéro règle à écrire
  msg.textContent = email.checkValidity() ? 'Adresse valide ✓' : email.validationMessage;
});
02

Types d'input riches

date, color, range, number, tel… chaque type déclenche le bon clavier mobile et le bon sélecteur natif — accessibles et cohérents avec l'OS. Aucune dépendance.

<label>Date <input type="date"></label>         <!-- sélecteur de date natif -->
<label>Couleur <input type="color" value="#c9952a"></label>  <!-- pipette -->
<label>Curseur <input type="range" min="0" max="100" value="60"></label>
<label>Nombre <input type="number" min="0" max="10" value="3"></label>
<label>Téléphone <input type="tel"></label>       <!-- clavier numérique sur mobile -->
03

datalist — autocomplétion native

Un <datalist> propose des suggestions à un champ texte, tout en laissant l'utilisateur saisir librement. Zéro JS, zéro plugin d'autocomplétion. Tape « J » ci-dessous.

<input list="technos" placeholder="Commence à taper…">
<datalist id="technos">
  <option value="JavaScript">
  <option value="CSS">
  <option value="SVG">
  <option value="HTML">
  <option value="WebAssembly">
</datalist>
<!-- suggestions natives, saisie libre conservée, zéro JS -->
04

Validation personnalisée

setCustomValidity() branche tes propres règles sur le mécanisme natif : le champ devient :invalid, le message s'affiche comme un message natif. Ici : le mot doit contenir « owl ».

<form id="form" novalidate>
  <label for="pw">Mot de passe (doit contenir « owl »)</label>
  <input type="text" id="pw" placeholder="super-owl-2026">
  <button type="submit">Valider</button>
  <p id="msg" role="status" aria-live="polite"></p>
</form>
const input = document.getElementById('pw');
const msg   = document.getElementById('msg');

function check() {
  // branche une règle perso sur le mécanisme natif (le champ devient :invalid)
  input.setCustomValidity(input.value.includes('owl') ? '' : 'Le mot doit contenir « owl ».');
}
input.addEventListener('input', check);

document.getElementById('form').addEventListener('submit', (e) => {
  e.preventDefault();
  check();
  msg.textContent = input.checkValidity() ? 'Parfait, « owl » est bien là ✓' : input.validationMessage;
});
05

:user-valid / :user-invalid

:invalid colore le champ en rouge avant même toute saisie. Les pseudo-classes modernes :user-valid / :user-invalid n'agissent qu'après interaction — bien plus agréable. Compare.

<label>:invalid (rouge d'emblée)
  <input class="eager" type="email" required placeholder="requis">
</label>
<label>:user-invalid (après saisie)
  <input class="lazy" type="email" required placeholder="requis">
</label>
input {
  padding: .5rem; color: #c9d4e8;
  background: #0d1117; border: 1px solid #1e2d44; border-radius: 6px;
}
/* rouge dès le chargement — trop agressif */
.eager:invalid { border-color: #f87171; }

/* rouge seulement APRÈS que l'utilisateur a touché le champ */
.lazy:user-invalid { border-color: #f87171; }
.lazy:user-valid   { border-color: #4ade80; }
06

FormData & soumission sans rechargement

new FormData(form) sérialise tout le formulaire en un objet prêt pour fetch() — envoi asynchrone, sans rechargement de page. Remplis et soumets pour voir les données collectées.


                
<form id="form">
  <label>Nom <input name="nom" value="OWL"></label>
  <label>Rôle <input name="role" value="Studio digital"></label>
  <button type="submit">Sérialiser</button>
  <pre id="output"></pre>
</form>
document.getElementById('form').addEventListener('submit', (e) => {
  e.preventDefault();
  const data = new FormData(e.target);      // sérialise tout le formulaire
  const obj  = Object.fromEntries(data);    // { nom: 'OWL', role: '…' }
  document.getElementById('output').textContent = JSON.stringify(obj, null, 2);
  // fetch('/api', { method: 'POST', body: data });  // envoi async, sans rechargement
});