Les formulaires HTML

Maîtrisez la création de formulaires interactifs et accessibles : inputs, validation, UX et sécurité.

Syntaxe de base des formulaires

Les formulaires HTML permettent de collecter et envoyer des données utilisateur vers un serveur.

Structure de base d’un formulaire

Éléments essentiels

html

<form> ← Conteneur principal
    <label> ← Étiquette
    <input> ← Champ de saisie
    <button> ← Bouton d'action
</form>

Exemple minimal

html

<form action="/submit" method="POST">
    <label for="name">Nom :</label>
    <input type="text" id="name" name="name">
    <button type="submit">Envoyer</button>
</form>

Rendu interactif

Le formulaire est réel : la validation HTML5 s’y applique. La soumission, elle, est bloquée — un aperçu est un document isolé.

Éléments de formulaire

Découvrez tous les types d’inputs et éléments disponibles pour créer des formulaires riches.

Validation HTML5

Utilisez les attributs de validation natifs pour améliorer l’expérience utilisateur.

Attributs de validation

  • required

    Rend un champ obligatoire

    • <input type="email" required>
  • pattern

    Expression régulière pour valider le format

    • <input pattern="[0-9]{5}" title="Code postal">
  • min/max & minlength/maxlength

    Contraintes de valeur et de longueur

    • <input type="number" min="18" max="65">
    • <input minlength="8" maxlength="20">

Messages personnalisés

  • title

    Message d’aide pour l’utilisateur

    • title="Entrez votre code postal (5 chiffres)"
  • JavaScript personnalisé

    Validation avec setCustomValidity()

    • input.setCustomValidity("Message d’erreur");
  • Pseudo-classes CSS

    Styles pour les états de validation

    • :valid { border-color: green; }
    • :invalid { border-color: red; }

Exemple : formulaire avec validation complète

Code HTML avec validation

html

<form novalidate>
    <div>
    <label for="email">Email *</label>
    <input
    type="email"
    id="email"
    required
    placeholder="nom@exemple.com"
    >
    </div>
    <div>
    <label for="password">Mot de passe *</label>
    <input
    type="password"
    id="password"
    required
    minlength="8"
    pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}"
    title="Au moins 8 caractères avec majuscule, minuscule et chiffre"
    >
    </div>
    <div>
    <label for="age">Âge</label>
    <input
    type="number"
    id="age"
    min="13"
    max="120"
    >
    </div>
    <button type="submit">S'inscrire</button>
</form>

Formulaire fonctionnel

Saisissez une adresse invalide, puis validez : c’est le navigateur qui refuse, sans une ligne de JavaScript.

Bonnes pratiques

Créez des formulaires accessibles, ergonomiques et sécurisés qui offrent une excellente expérience utilisateur.

Travaux pratiques

Créez des formulaires professionnels et interactifs avec ces exercices progressifs.

  1. Exercice 1 : formulaire de contact professionnel

    Objectif

    Créez un formulaire de contact complet avec validation, accessibilité et design soigné.

    Spécifications

    • Informations personnelles (nom, email, téléphone)
    • Sujet avec liste déroulante pré-définie
    • Message avec compteur de caractères
    • Consentement RGPD obligatoire
    • Validation complète et messages d’erreur

    Aperçu attendu

    Chaque champ a son label associé : c’est ce qui rend le formulaire utilisable au clavier et au lecteur d’écran.
  2. Exercice 2 : formulaire d’inscription avancé

    Mission

    Créez un formulaire d’inscription complexe avec tous les types d’inputs et une validation poussée.

    Défis techniques

    • Validation en temps réel avec JavaScript
    • Upload d’avatar avec prévisualisation
    • Sélecteur de date de naissance
    • Choix multiples avec checkboxes
    • Barre de progression du formulaire

    Fonctionnalités attendues

    Sept types d’input différents, tous natifs : le navigateur fournit déjà le sélecteur de date, le curseur et le sélecteur de couleur.

Code de démarrage

html

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Exercices - Formulaires HTML</title>
    <style>
        /* Styles de base pour les formulaires */
        * { box-sizing: border-box; }
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            margin: 0;
            padding: 20px;
            background: #f5f7fa;
        }

        /* Conteneurs */
        .container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }

        /* Formulaires */
        form { margin-bottom: 30px; }

        fieldset {
            border: 2px solid #e1e8ed;
            border-radius: 8px;
            padding: 20px;
            margin-bottom: 20px;
        }

        legend {
            font-weight: bold;
            color: #2c3e50;
            padding: 0 10px;
        }

        /* Labels et inputs */
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: 500;
            color: #34495e;
        }

        input, textarea, select {
            width: 100%;
            padding: 12px;
            border: 2px solid #bdc3c7;
            border-radius: 6px;
            font-size: 16px;
            transition: border-color 0.3s, box-shadow 0.3s;
        }

        input:focus, textarea:focus, select:focus {
            outline: none;
            border-color: #3498db;
            box-shadow: 0 0 0 3px rgba(52,152,219,0.2);
        }

        /* États de validation */
        input:valid { border-color: #27ae60; }
        input:invalid:not(:focus):not(:placeholder-shown) { border-color: #e74c3c; }

        /* Types spéciaux */
        input[type="checkbox"], input[type="radio"] {
            width: auto;
            margin-right: 8px;
        }

        input[type="file"] {
            border: none;
            padding: 8px 0;
        }

        /* Boutons */
        button {
            background: #3498db;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 6px;
            font-size: 16px;
            cursor: pointer;
            transition: background-color 0.3s;
        }

        button:hover { background: #2980b9; }
        button:disabled {
            background: #bdc3c7;
            cursor: not-allowed;
        }

        /* Layout */
        .form-group { margin-bottom: 20px; }
        .form-row {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 15px;
        }

        /* Messages d'aide */
        .help-text {
            font-size: 14px;
            color: #7f8c8d;
            margin-top: 5px;
        }

        .error-message {
            color: #e74c3c;
            font-size: 14px;
            margin-top: 5px;
        }

        /* Responsive */
        @media (max-width: 768px) {
            .form-row { grid-template-columns: 1fr; }
            .container { margin: 10px; padding: 20px; }
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>Exercices - Formulaires HTML</h1>

        <!-- EXERCICE 1 : FORMULAIRE DE CONTACT -->
        <section>
            <h2>Exercice 1 : Formulaire de contact professionnel</h2>

            <form action="/contact" method="POST" novalidate>
                <!-- TODO: Champ token CSRF -->
                <input type="hidden" name="_token" value="csrf_token_here">

                <!-- TODO: Fieldset informations personnelles -->
                <fieldset>
                    <legend>Informations personnelles</legend>

                    <div class="form-row">
                        <div class="form-group">
                            <label for="lastname">Nom *</label>
                            <input
                                type="text"
                                id="lastname"
                                name="lastname"
                                required
                                minlength="2"
                                maxlength="50"
                                autocomplete="family-name"
                                placeholder="Votre nom de famille"
                            >
                        </div>

                        <!-- TODO: Champ prénom -->
                        <div class="form-group">
                            <label for="firstname">Prénom</label>
                            <input
                                type="text"
                                id="firstname"
                                name="firstname"
                                maxlength="50"
                                autocomplete="given-name"
                                placeholder="Votre prénom"
                            >
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group">
                            <label for="email">Email *</label>
                            <input
                                type="email"
                                id="email"
                                name="email"
                                required
                                autocomplete="email"
                                placeholder="nom@exemple.com"
                            >
                        </div>

                        <!-- TODO: Champ téléphone -->
                        <div class="form-group">
                            <label for="phone">Téléphone</label>
                            <input
                                type="tel"
                                id="phone"
                                name="phone"
                                pattern="[0-9]{10}"
                                autocomplete="tel"
                                placeholder="0123456789"
                                title="Numéro de téléphone à 10 chiffres"
                            >
                            <div class="help-text">Format: 10 chiffres sans espaces</div>
                        </div>
                    </div>
                </fieldset>

                <!-- TODO: Sujet du message -->
                <div class="form-group">
                    <label for="subject">Sujet *</label>
                    <select id="subject" name="subject" required>
                        <option value="">Choisissez un sujet</option>
                        <option value="info">Demande d'information</option>
                        <option value="support">Support technique</option>
                        <!-- TODO: Ajoutez d'autres options -->
                        <option value="commercial">Question commerciale</option>
                        <option value="partnership">Partenariat</option>
                        <option value="other">Autre</option>
                    </select>
                </div>

                <!-- TODO: Message -->
                <div class="form-group">
                    <label for="message">Message *</label>
                    <textarea
                        id="message"
                        name="message"
                        required
                        minlength="10"
                        maxlength="1000"
                        rows="5"
                        placeholder="Décrivez votre demande en détail..."
                    ></textarea>
                    <div class="help-text">Minimum 10 caractères, maximum 1000</div>
                </div>

                <!-- TODO: Cases à cocher -->
                <div class="form-group">
                    <label>
                        <input type="checkbox" name="newsletter" value="1">
                        Je souhaite recevoir la newsletter
                    </label>
                </div>

                <div class="form-group">
                    <label>
                        <input type="checkbox" name="terms" required>
                        J'accepte les <a href="/terms" target="_blank">conditions d'utilisation</a> *
                    </label>
                </div>

                <!-- TODO: Boutons d'action -->
                <div class="form-group">
                    <button type="submit">Envoyer le message</button>
                    <button type="reset" style="margin-left: 10px; background: #95a5a6;">Effacer</button>
                </div>
            </form>
        </section>

        <hr style="margin: 40px 0;">

        <!-- EXERCICE 2 : FORMULAIRE D'INSCRIPTION -->
        <section>
            <h2>Exercice 2 : Formulaire d'inscription avancé</h2>

            <form action="/register" method="POST" enctype="multipart/form-data" novalidate>
                <!-- TODO: Upload d'avatar -->
                <div class="form-group" style="text-align: center;">
                    <label for="avatar">Photo de profil</label>
                    <div id="avatar-preview" style="width: 100px; height: 100px; border: 2px dashed #bdc3c7; border-radius: 50%; margin: 10px auto; display: flex; align-items: center; justify-content: center; background: #ecf0f1;">
                        📷
                    </div>
                    <input
                        type="file"
                        id="avatar"
                        name="avatar"
                        accept="image/jpeg,image/png,image/gif"
                    >
                    <div class="help-text">Formats acceptés: JPG, PNG, GIF (max 2MB)</div>
                </div>

                <!-- TODO: Informations de compte -->
                <div class="form-row">
                    <div class="form-group">
                        <label for="username">Nom d'utilisateur *</label>
                        <input
                            type="text"
                            id="username"
                            name="username"
                            required
                            minlength="3"
                            maxlength="20"
                            pattern="[a-zA-Z0-9_-]{3,20}"
                            autocomplete="username"
                            placeholder="mon_nom_utilisateur"
                        >
                        <div class="help-text">3-20 caractères, lettres, chiffres, _ et - autorisés</div>
                    </div>

                    <div class="form-group">
                        <label for="register-email">Email *</label>
                        <input
                            type="email"
                            id="register-email"
                            name="email"
                            required
                            autocomplete="email"
                            placeholder="nom@exemple.com"
                        >
                    </div>
                </div>

                <!-- TODO: Mots de passe -->
                <div class="form-row">
                    <div class="form-group">
                        <label for="password">Mot de passe *</label>
                        <input
                            type="password"
                            id="confirm-password"
                            name="confirm_password"
                            required
                            autocomplete="new-password"
                        >
                        <div id="password-match" class="help-text"></div>
                    </div>
                </div>

                <!-- TODO: Informations personnelles -->
                <fieldset>
                    <legend>Informations personnelles</legend>

                    <div class="form-row">
                        <div class="form-group">
                            <label for="birthdate">Date de naissance</label>
                            <input
                                type="date"
                                id="birthdate"
                                name="birthdate"
                                min="1900-01-01"
                                max="2010-12-31"
                                autocomplete="bday"
                            >
                        </div>

                        <div class="form-group">
                            <label>Genre</label>
                            <div style="display: flex; gap: 15px; margin-top: 5px;">
                                <label>
                                    <input type="radio" name="gender" value="M">
                                    Homme
                                </label>
                                <label>
                                    <input type="radio" name="gender" value="F">
                                    Femme
                                </label>
                                <label>
                                    <input type="radio" name="gender" value="O">
                                    Autre
                                </label>
                            </div>
                        </div>
                    </div>
                </fieldset>

                <!-- TODO: Préférences et intérêts -->
                <fieldset>
                    <legend>Préférences</legend>

                    <div class="form-group">
                        <label>Centres d'intérêt (plusieurs choix possibles)</label>
                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;">
                            <label>
                                <input type="checkbox" name="interests[]" value="tech">
                                Technologie
                            </label>
                            <label>
                                <input type="checkbox" name="interests[]" value="design">
                                Design
                            </label>
                            <label>
                                <input type="checkbox" name="interests[]" value="business">
                                Business
                            </label>
                            <label>
                                <input type="checkbox" name="interests[]" value="marketing">
                                Marketing
                            </label>
                            <label>
                                <input type="checkbox" name="interests[]" value="education">
                                Éducation
                            </label>
                            <label>
                                <input type="checkbox" name="interests[]" value="health">
                                Santé
                            </label>
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="experience">Niveau d'expérience</label>
                        <input
                            type="range"
                            id="experience"
                            name="experience"
                            min="0"
                            max="10"
                            value="5"
                            oninput="document.getElementById('experience-value').textContent = this.value"
                        >
                        <div class="help-text">
                            Années d'expérience: <span id="experience-value">5</span>
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="theme-color">Couleur de thème préférée</label>
                        <input
                            type="color"
                            id="theme-color"
                            name="theme_color"
                            value="#3498db"
                            style="width: 60px; height: 40px; padding: 2px;"
                        >
                    </div>
                </fieldset>

                <!-- TODO: Acceptation des conditions -->
                <div class="form-group">
                    <label>
                        <input type="checkbox" name="newsletter_signup" value="1">
                        Je souhaite recevoir des notifications par email
                    </label>
                </div>

                <div class="form-group">
                    <label>
                        <input type="checkbox" name="terms_register" required>
                        J'accepte les <a href="/terms" target="_blank">conditions d'utilisation</a> et la <a href="/privacy" target="_blank">politique de confidentialité</a> *
                    </label>
                </div>

                <!-- TODO: Boutons d'action -->
                <div class="form-group">
                    <button type="submit" style="background: #27ae60;">Créer mon compte</button>
                    <button type="button" onclick="resetFormWithConfirm()" style="margin-left: 10px; background: #95a5a6;">Réinitialiser</button>
                </div>
            </form>
        </section>

        <!-- Instructions pour les étudiants -->
        <section style="margin-top: 40px; padding: 20px; background: #f8f9fa; border-radius: 8px;">
            <h2>Instructions pour les exercices</h2>

            <h3>Exercice 1 - Points à améliorer :</h3>
            <ul>
                <li>Ajouter une validation en temps réel avec JavaScript</li>
                <li>Implémenter des messages d'erreur personnalisés</li>
                <li>Améliorer l'accessibilité avec aria-describedby</li>
                <li>Ajouter un indicateur de progression</li>
                <li>Tester la soumission et gérer les états de chargement</li>
            </ul>

            <h3>Exercice 2 - Défis avancés :</h3>
            <ul>
                <li>Implémenter la prévisualisation d'avatar en temps réel</li>
                <li>Créer un indicateur de force de mot de passe dynamique</li>
                <li>Ajouter la vérification de disponibilité du nom d'utilisateur</li>
                <li>Implémenter une sauvegarde automatique en brouillon</li>
                <li>Créer des animations de feedback pour les validations</li>
            </ul>

            <h3>Bonnes pratiques à appliquer :</h3>
            <ul>
                <li>Accessibilité : Labels, roles ARIA, navigation clavier</li>
                <li>Performance : Debounce des validations, lazy loading</li>
                <li>Sécurité : Validation côté serveur, protection CSRF</li>
                <li>UX : Messages clairs, progression visuelle, états de chargement</li>
            </ul>
        </section>
    </div>

    <!-- JavaScript pour les fonctionnalités avancées -->
    <script>
        // Fonction de validation de démonstration
        function showValidationDemo() {
            const result = document.getElementById('validation-result');
            result.className = 'mt-4 p-3 rounded bg-green-100 text-green-800';
            result.textContent = '✅ Formulaire validé avec succès !';
            result.classList.remove('hidden');

            setTimeout(() => {
                result.classList.add('hidden');
            }, 3000);
        }

        // Fonction de réinitialisation avec confirmation
        function resetFormWithConfirm() {
            if (confirm('Êtes-vous sûr de vouloir effacer tous les champs ?')) {
                document.querySelector('form[action="/register"]').reset();
                // Réinitialiser les éléments personnalisés
                document.getElementById('experience-value').textContent = '5';
                document.getElementById('password-strength').innerHTML = 'Force du mot de passe: <span style="color: #e74c3c;">●○○○</span> Faible';
                document.getElementById('password-match').textContent = '';
            }
        }

        // Validation en temps réel pour les mots de passe
        document.addEventListener('DOMContentLoaded', function() {
            const password = document.getElementById('password');
            const confirmPassword = document.getElementById('confirm-password');
            const strengthIndicator = document.getElementById('password-strength');
            const matchIndicator = document.getElementById('password-match');

            if (password && strengthIndicator) {
                password.addEventListener('input', function() {
                    const value = this.value;
                    let strength = 0;
                    let indicators = ['○', '○', '○', '○'];
                    let label = 'Très faible';
                    let color = '#e74c3c';

                    if (value.length >= 8) strength++;
                    if (/[a-z]/.test(value)) strength++;
                    if (/[A-Z]/.test(value)) strength++;
                    if (/[0-9]/.test(value)) strength++;
                    if (/[^A-Za-z0-9]/.test(value)) strength++;

                    for (let i = 0; i < strength && i < 4; i++) {
                        indicators[i] = '●';
                    }

                    if (strength >= 4) { label = 'Forte'; color = '#27ae60'; }
                    else if (strength >= 3) { label = 'Moyenne'; color = '#f39c12'; }
                    else if (strength >= 2) { label = 'Faible'; color = '#e67e22'; }

                    strengthIndicator.innerHTML = `Force du mot de passe: <span style="color: ${color}">${indicators.join('')}</span> ${label}`;
                });
            }

            if (confirmPassword && matchIndicator) {
                function checkPasswordMatch() {
                    const pass1 = password ? password.value : '';
                    const pass2 = confirmPassword.value;

                    if (pass2.length > 0) {
                        if (pass1 === pass2) {
                            matchIndicator.textContent = '✅ Les mots de passe correspondent';
                            matchIndicator.style.color = '#27ae60';
                        } else {
                            matchIndicator.textContent = '❌ Les mots de passe ne correspondent pas';
                            matchIndicator.style.color = '#e74c3c';
                        }
                    } else {
                        matchIndicator.textContent = '';
                    }
                }

                confirmPassword.addEventListener('input', checkPasswordMatch);
                if (password) password.addEventListener('input', checkPasswordMatch);
            }

            // Prévisualisation d'avatar
            const avatarInput = document.getElementById('avatar');
            const avatarPreview = document.getElementById('avatar-preview');

            if (avatarInput && avatarPreview) {
                avatarInput.addEventListener('change', function(e) {
                    const file = e.target.files[0];
                    if (file) {
                        const reader = new FileReader();
                        reader.onload = function(e) {
                            avatarPreview.innerHTML = `<img src="${e.target.result}" style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%;">`;
                        };
                        reader.readAsDataURL(file);
                    }
                });
            }
        });
    </script>
</body>
</html>

Collectez des données avec style

Vous maîtrisez maintenant les formulaires HTML sous toutes leurs formes. Explorez les autres aspects du HTML pour créer des sites web complets et interactifs.