FVOweb

Créer un formulaire moderne avec validation CSS

?? Sommaire

?? Introduction

Les formulaires sont le point de contact principal entre vos utilisateurs et votre site. Un formulaire bien conçu peut augmenter significativement votre taux de conversion, tandis qu'un formulaire mal pensé peut frustrer et faire fuir vos visiteurs.

Dans ce tutoriel complet, vous allez apprendre à créer des formulaires modernes et élégants avec CSS pur. Nous couvrirons les labels flottants, la validation en temps réel, les animations d'inputs et toutes les bonnes pratiques UX.

?? Saviez-vous ? Les formulaires avec validation visuelle en temps réel ont un taux de complétion 22% plus élevé que ceux sans feedback visuel. Le CSS moderne permet de créer cette expérience sans une ligne de JavaScript !

??? Les pseudo-classes de formulaire

CSS offre de nombreuses pseudo-classes spécialement conçues pour les formulaires. Ces sélecteurs puissants permettent de styliser les inputs selon leur état.

Les pseudo-classes essentielles

Pseudo-classe Description Exemple d'utilisation
:focus Élément actuellement sélectionné Bordure bleue quand on clique
:focus-within Parent contenant un élément focus Styliser le conteneur du form
:valid Input avec valeur valide Bordure verte de succès
:invalid Input avec valeur invalide Bordure rouge d'erreur
:placeholder-shown Input affichant son placeholder Labels flottants
:required Champ obligatoire Ajouter un astérisque
:disabled Champ désactivé Opacité réduite
:read-only Champ en lecture seule Style grisé non-éditable
:checked Checkbox/radio coché Changer la couleur
Cycle de vie d'un input Vide :focus Remplissage :valid ? :invalid ? clic tape Email correct Email invalide

L'astuce du :placeholder-shown

La pseudo-classe :placeholder-shown est la clé des labels flottants. Elle détecte si le placeholder est visible (donc si l'input est vide).

CSS - Détection input vide vs rempli
/* Le placeholder est visible = input vide */
input:placeholder-shown + label {
    /* Label en position normale */
    top: 50%;
    font-size: 16px;
}

/* Le placeholder est caché = input rempli */
input:not(:placeholder-shown) + label {
    /* Label flotte vers le haut */
    top: 0;
    font-size: 12px;
    color: #0066ff;
}
?? Important : Pour que :placeholder-shown fonctionne, votre input DOIT avoir un attribut placeholder, même s'il est vide : placeholder=" "

? Styliser les inputs

Reset des styles navigateur

Avant de styliser, il faut réinitialiser les styles par défaut des navigateurs qui varient beaucoup.

CSS - Reset des inputs
/* Reset complet des inputs */
input, textarea, select {
    appearance: none;           /* Supprime les styles natifs */
    -webkit-appearance: none;
    border: none;
    outline: none;
    background: none;
    font-family: inherit;
    font-size: inherit;
    color: inherit;
}

/* Supprimer le outline bleu par défaut */
input:focus {
    outline: none;
}

/* Cacher le X de suppression sur IE/Edge */
input::-ms-clear {
    display: none;
}

/* Styliser le placeholder */
input::placeholder {
    color: #888;
    opacity: 1;
}

Structure HTML recommandée

Une bonne structure HTML est essentielle pour les labels flottants :

HTML - Structure pour label flottant
<div class="input-group">
    <input 
        type="email" 
        id="email" 
        placeholder=" " 
        required
    >
    <label for="email">Adresse email</label>
</div>

<!-- Note : le placeholder=" " (espace) est obligatoire 
     pour que :placeholder-shown fonctionne -->

✅ Validation CSS en temps réel

La validation HTML5 combinée au CSS permet de créer des feedbacks visuels instantanés sans JavaScript.

Attributs de validation HTML5

Attribut Validation Exemple
required Champ obligatoire <input required>
type="email" Format email valide Vérifie @domaine.com
minlength Longueur minimale minlength="8"
maxlength Longueur maximale maxlength="100"
pattern Expression régulière pattern="[A-Za-z]+"
min / max Valeur min/max (nombres) min="0" max="100"

CSS de validation complet

CSS - Validation en temps réel
/* État par défaut */
.input-group input {
    border: 2px solid rgba(255, 255, 255, 0.1);
    transition: all 0.3s ease;
}

/* Focus - bordure bleue */
.input-group input:focus {
    border-color: #0066ff;
    box-shadow: 0 0 0 4px rgba(0, 102, 255, 0.1);
}

/* Valide ET rempli - bordure verte */
.input-group input:valid:not(:placeholder-shown) {
    border-color: #00c853;
}

/* Invalide ET rempli ET pas focus - bordure rouge */
.input-group input:invalid:not(:placeholder-shown):not(:focus) {
    border-color: #ff3d57;
}

/* Icône de validation */
.input-group input:valid:not(:placeholder-shown) ~ .icon-valid {
    opacity: 1;
    color: #00c853;
}

.input-group input:invalid:not(:placeholder-shown):not(:focus) ~ .icon-invalid {
    opacity: 1;
    color: #ff3d57;
}
?? Astuce UX : N'affichez les erreurs que quand l'utilisateur a quitté le champ (:not(:focus)). Afficher une erreur pendant qu'il tape est frustrant !

?? 9 Exemples pratiques

Exemple 1 : Label flottant (Floating Label)

Le label se déplace vers le haut quand l'input reçoit le focus ou contient du texte. C'est le pattern le plus utilisé en 2025.

CSS - Label flottant complet
.input-group {
    position: relative;
    margin-bottom: 25px;
}

.input-group input {
    width: 100%;
    padding: 16px 14px;
    font-size: 16px;
    color: #e0e0e0;
    background: rgba(255, 255, 255, 0.05);
    border: 2px solid rgba(255, 255, 255, 0.1);
    border-radius: 8px;
    outline: none;
    transition: all 0.3s ease;
}

.input-group label {
    position: absolute;
    left: 14px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 16px;
    color: #888;
    pointer-events: none;
    transition: all 0.3s ease;
    background: #000;  /* Couleur du fond */
    padding: 0 5px;
}

/* Focus : bordure bleue + label monte */
.input-group input:focus {
    border-color: #0066ff;
}

.input-group input:focus + label,
.input-group input:not(:placeholder-shown) + label {
    top: 0;
    font-size: 12px;
    color: #0066ff;
}

/* Validation */
.input-group input:valid:not(:placeholder-shown) {
    border-color: #00c853;
}

.input-group input:valid:not(:placeholder-shown) + label {
    color: #00c853;
}
Exemple 2 : Input avec underline animée

Une ligne bleue qui s'étend du centre vers les extrémités au focus. Style minimaliste très élégant inspiré de Material Design.

CSS - Underline animée
.input-underline {
    position: relative;
    margin-bottom: 30px;
}

.input-underline input {
    width: 100%;
    padding: 12px 0;
    font-size: 16px;
    color: #e0e0e0;
    background: transparent;
    border: none;
    border-bottom: 2px solid rgba(255, 255, 255, 0.2);
}

/* La ligne animée */
.input-underline .underline {
    position: absolute;
    bottom: 0;
    left: 50%;           /* Commence au centre */
    width: 0;
    height: 2px;
    background: #0066ff;
    transition: all 0.3s ease;
}

/* Au focus : la ligne s'étend */
.input-underline input:focus ~ .underline {
    left: 0;
    width: 100%;
}

/* Label flottant */
.input-underline label {
    position: absolute;
    left: 0;
    top: 12px;
    color: #888;
    pointer-events: none;
    transition: all 0.3s ease;
}

.input-underline input:focus + label,
.input-underline input:not(:placeholder-shown) + label {
    top: -20px;
    font-size: 12px;
    color: #0066ff;
}
Exemple 3 : Input avec icône

Des icônes qui changent de couleur au focus pour guider l'utilisateur.

CSS - Input avec icône
.input-icon {
    position: relative;
}

/* Padding gauche pour l'icône */
.input-icon input {
    padding-left: 45px;
}

/* Label aussi décalé pour éviter le chevauchement */
.input-icon label {
    left: 45px;
}

/* Icône positionnée à gauche */
.input-icon .icon {
    position: absolute;
    left: 14px;
    top: 50%;
    transform: translateY(-50%);
    color: #888;
    transition: color 0.3s ease;
}

/* L'icône devient bleue au focus */
.input-icon input:focus ~ .icon {
    color: #0066ff;
}

/* L'icône devient verte si valide */
.input-icon input:valid:not(:placeholder-shown) ~ .icon {
    color: #00c853;
}

/* L'icône devient rouge si invalide */
.input-icon input:invalid:not(:placeholder-shown):not(:focus) ~ .icon {
    color: #ff3d57;
}
Exemple 4 : Input avec effet Glow

Un effet de lueur subtil au focus pour un look futuriste. Inspiré de la collection FVOweb.

CSS - Input Glow Effect
.input-glow input {
    background: rgba(0, 0, 0, 0.3);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 8px;
    transition: all 0.3s ease;
}

.input-glow input:focus {
    border-color: #0066ff;
    box-shadow: 
        0 0 10px rgba(0, 102, 255, 0.3),
        0 0 20px rgba(0, 102, 255, 0.2),
        0 0 30px rgba(0, 102, 255, 0.1);
}
Exemple 5 : Container avec :focus-within

Le conteneur entier s'illumine quand un de ses champs est actif. Parfait pour les formulaires de connexion.

CSS - Focus-within sur le conteneur
.form-focus-within {
    padding: 30px;
    border: 2px solid rgba(255, 255, 255, 0.1);
    border-radius: 12px;
    transition: all 0.3s ease;
}

/* Quand N'IMPORTE QUEL enfant a le focus */
.form-focus-within:focus-within {
    border-color: #0066ff;
    box-shadow: 0 0 30px rgba(0, 102, 255, 0.1);
}

/* Bonus: changer le titre quand le form est actif */
.form-focus-within:focus-within h2 {
    color: #0066ff;
}

:focus-within est très puissant ! Il permet de styliser n'importe quel parent quand un de ses descendants a le focus.

Exemple 6 : Validation CSS en temps réel
? ? Format email invalide
? ? Min 3 caractères, alphanumériques

Validation native HTML5 stylisée avec CSS pur. Aucun JavaScript nécessaire ! Les pseudo-classes :valid et :invalid détectent automatiquement l'état.

CSS - Validation en temps réel
/* Style de base de l'input */
.input-group input {
    width: 100%;
    padding: 14px 40px 14px 16px; /* Espace pour l'icône */
    border: 2px solid rgba(255, 255, 255, 0.1);
    border-radius: 8px;
    background: transparent;
    color: #fff;
    transition: all 0.3s ease;
}

/* Icônes de validation (cachées par défaut) */
.validation-icon {
    position: absolute;
    right: 14px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 18px;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.valid-icon { color: #00c853; }
.invalid-icon { color: #ff3d57; }

/* Message d'erreur (caché par défaut) */
.validation-msg {
    position: absolute;
    bottom: -22px;
    left: 0;
    font-size: 12px;
    color: #ff3d57;
    opacity: 0;
    transition: opacity 0.3s ease;
}

/* ✔️ État VALIDE - Bordure verte + icône check */
input:valid:not(:placeholder-shown) {
    border-color: #00c853;
}

input:valid:not(:placeholder-shown) ~ .valid-icon {
    opacity: 1;
}

/* ❌ État INVALIDE - Bordure rouge + icône X + message */
input:invalid:not(:placeholder-shown):not(:focus) {
    border-color: #ff3d57;
}

input:invalid:not(:placeholder-shown):not(:focus) ~ .invalid-icon {
    opacity: 1;
}

input:invalid:not(:placeholder-shown):not(:focus) ~ .validation-msg {
    opacity: 1;
}

💡 Important : Le sélecteur :not(:placeholder-shown) empêche la validation de s'afficher sur un champ vide. Et :not(:focus) attend que l'utilisateur quitte le champ avant d'afficher l'erreur.

?? Attributs HTML5 pour la validation :
required - Champ obligatoire
type="email" - Vérifie le format email
minlength="3" - Longueur minimum
pattern="[a-z]+" - Expression régulière personnalisée
Exemple 7 : Checkbox & Radio personnalisés

Checkboxes :

Radio buttons :

La technique du input:checked permet de styliser totalement les checkboxes et radios natifs sans JavaScript.

CSS - Custom Checkbox
/* Container cliquable */
.custom-checkbox {
    display: flex;
    align-items: center;
    gap: 12px;
    cursor: pointer;
}

/* Cacher l'input natif */
.custom-checkbox input {
    display: none;
}

/* Notre checkbox personnalisée */
.checkbox-box {
    width: 22px;
    height: 22px;
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-radius: 6px;
    position: relative;
    transition: all 0.3s ease;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Icône check (cachée par défaut) */
.checkbox-box::after {
    content: '?';
    font-size: 14px;
    color: white;
    opacity: 0;
    transform: scale(0);
    transition: all 0.2s ease;
}

/* État coché : fond bleu + check visible */
.custom-checkbox input:checked + .checkbox-box {
    background: #0066ff;
    border-color: #0066ff;
}

.custom-checkbox input:checked + .checkbox-box::after {
    opacity: 1;
    transform: scale(1);
}
CSS - Custom Radio Button
.custom-radio {
    display: flex;
    align-items: center;
    gap: 12px;
    cursor: pointer;
}

.custom-radio input {
    display: none;
}

/* Cercle extérieur */
.radio-circle {
    width: 22px;
    height: 22px;
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-radius: 50%;
    position: relative;
    transition: all 0.3s ease;
}

/* Point intérieur (caché par défaut) */
.radio-circle::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0);
    width: 10px;
    height: 10px;
    background: #0066ff;
    border-radius: 50%;
    transition: transform 0.2s ease;
}

/* État coché : bordure bleue + point visible */
.custom-radio input:checked + .radio-circle {
    border-color: #0066ff;
}

.custom-radio input:checked + .radio-circle::after {
    transform: translate(-50%, -50%) scale(1);
}
Exemple 8 : États :disabled et :read-only

Les états :disabled et :read-only permettent de styliser les champs non-éditables différemment.

CSS - États disabled et read-only
/* Champ désactivé - non cliquable */
input:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    background: rgba(255, 255, 255, 0.02);
}

input:disabled + label {
    color: rgba(255, 255, 255, 0.3);
    top: 0;        /* Label en position haute */
    font-size: 12px;
}

/* Champ read-only - visible mais non éditable */
input:read-only {
    opacity: 0.7;
    cursor: default;
    background: rgba(255, 255, 255, 0.02);
    border-style: dashed; /* Indicateur visuel */
}

input:read-only + label {
    color: rgba(255, 255, 255, 0.5);
    top: 0;
    font-size: 12px;
}

/* Note: read-only peut recevoir le focus contrairement à disabled */
input:read-only:focus {
    border-color: rgba(255, 255, 255, 0.3);
    box-shadow: none;
}
?? Différence clé :
disabled : Ne peut pas recevoir le focus, valeur non envoyée au serveur
readonly : Peut recevoir le focus, valeur envoyée au serveur
Exemple 9 : Formulaire de connexion complet

Ce formulaire combine toutes les techniques : labels flottants, icônes, validation, checkbox custom et :focus-within sur le conteneur.

HTML - Structure du formulaire
<form class="login-form">
    <h3>Connexion</h3>
    
    <div class="input-group input-icon">
        <input type="email" id="email" placeholder=" " required>
        <label for="email">Email</label>
        <i class="fas fa-envelope icon"></i>
    </div>
    
    <div class="input-group input-icon">
        <input type="password" id="pass" placeholder=" " required minlength="8">
        <label for="pass">Mot de passe</label>
        <i class="fas fa-lock icon"></i>
    </div>
    
    <label class="custom-checkbox">
        <input type="checkbox" name="remember">
        <span class="checkbox-box"></span>
        <span>Se souvenir de moi</span>
    </label>
    
    <button type="submit" class="submit-btn">Se connecter</button>
</form>
CSS - Conteneur avec :focus-within
.login-form {
    background: linear-gradient(145deg, #111111 0%, #0a0a0a 100%);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 20px;
    padding: 40px;
    max-width: 400px;
    transition: all 0.3s ease;
}

/* Tout le formulaire s'illumine quand actif */
.login-form:focus-within {
    border-color: #0066ff;
    box-shadow: 0 0 40px rgba(0, 102, 255, 0.1);
}

/* Bouton avec gradient */
.submit-btn {
    width: 100%;
    padding: 16px;
    font-weight: 600;
    color: white;
    background: linear-gradient(135deg, #0066ff 0%, #683FEA 100%);
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.submit-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 30px rgba(0, 102, 255, 0.3);
}

Retrouvez plus de formulaires dans la collection FVOweb

Tous ces exemples et bien d'autres sont disponibles gratuitement dans notre collection, prêts à copier-coller !

Voir tous les formulaires

? Accessibilité des formulaires

Règles essentielles

  • Labels associés : Toujours utiliser for="id" pour lier label et input
  • Focus visible : Ne jamais supprimer outline sans alternative visible
  • Contraste suffisant : Ratio minimum 4.5:1 pour le texte
  • Messages d'erreur clairs : Pas seulement une couleur rouge
  • Navigation clavier : Tab doit parcourir tous les champs
CSS - Focus accessible
/* ? MAUVAIS - Supprime l'indication de focus */
input:focus {
    outline: none;
}

/* ? BON - Remplace par un style visible */
input:focus {
    outline: none;
    border-color: #0066ff;
    box-shadow: 0 0 0 4px rgba(0, 102, 255, 0.2);
}

/* ? Pour les utilisateurs clavier uniquement */
input:focus-visible {
    outline: 2px solid #0066ff;
    outline-offset: 2px;
}
?? Conseil accessibilité : Utilisez :focus-visible pour afficher un outline uniquement lors de la navigation clavier, pas au clic souris. C'est le meilleur des deux mondes !

?? Conclusion

Vous avez maintenant tous les outils pour créer des formulaires modernes et accessibles avec CSS pur. Voici les points clés à retenir :

  • ? Utilisez :placeholder-shown pour les labels flottants
  • ? Combinez :valid et :invalid pour la validation visuelle
  • ? N'affichez les erreurs qu'après le blur (:not(:focus))
  • ? Utilisez :focus-within pour styliser les conteneurs
  • ? Toujours garder un focus visible pour l'accessibilité
  • ? Testez la navigation clavier

Pour aller plus loin

N'hésitez pas à expérimenter et combiner ces techniques pour créer vos propres designs uniques !