Labels et associations

Mauvais exemple

Connexion

Se souvenir de moi
<!-- ❌ Mauvais : Labels manquants -->
<form>
  <h4>Connexion</h4>

  <!-- Pas de label associé -->
  <input
    type="email"
    placeholder="Votre email">

  <!-- Pas de label associé, pas d'autocomplete -->
  <input
    type="password"
    placeholder="Mot de passe">

  <!-- Checkbox non associée -->
  <input type="checkbox">
  <span>Se souvenir de moi</span>

  <button type="submit">Se connecter</button>
</form>

Bon exemple

Connexion

Nous ne partagerons jamais votre email
Minimum 8 caractères avec majuscules et chiffres
<!-- ✅ Bon : Labels explicites et associés -->
<form>
  <h4>Connexion</h4>

  <!-- Label associé via for/id -->
  <label for="email-good">Adresse email</label>
  <input
    type="email"
    id="email-good"
    placeholder="exemple@domaine.fr"
    required
    aria-describedby="email-help"
    autocomplete="email">
  <div id="email-help">
    Nous ne partagerons jamais votre email
  </div>

  <!-- Label avec instructions -->
  <label for="password-good">Mot de passe</label>
  <input
    type="password"
    id="password-good"
    required
    aria-describedby="password-help"
    autocomplete="current-password">
  <div id="password-help">
    Minimum 8 caractères avec majuscules et chiffres
  </div>

  <!-- Checkbox correctement associée -->
  <input type="checkbox" id="remember-good">
  <label for="remember-good">Se souvenir de moi</label>

  <button type="submit">Se connecter</button>
</form>

Explication : Chaque champ de formulaire doit avoir un label explicite et correctement associé. Les placeholders ne remplacent pas les labels.

Validation et messages d'erreur

Mauvais exemple

Inscription newsletter

<!-- ❌ Mauvais : Erreurs mal annoncées -->
<form @submit.prevent="submitBadForm">
  <h4>Inscription newsletter</h4>

  <!-- Pas d'association label-input -->
  <label>Nom</label>
  <input
    type="text"
    v-model="badForm.name"
    :class="{ 'error': submitted && !badForm.name }">

  <!-- Erreur visuelle seulement -->
  <label>Email</label>
  <input
    type="email"
    v-model="badForm.email"
    :class="{ 'error': submitted && !isValidEmail(badForm.email) }">

  <!-- Pas d'information sur l'erreur -->
  <label>Âge</label>
  <input
    type="number"
    v-model="badForm.age"
    :class="{ 'error': submitted && badForm.age < 16 }">

  <!-- Message d'erreur générique et inutile -->
  <div v-if="submitted && badFormErrors.length">
    ❌ Veuillez corriger les erreurs
  </div>

  <button type="submit">S'inscrire</button>
</form>

Bon exemple

Inscription newsletter

Vous devez avoir au moins 16 ans
<!-- ✅ Bon : Erreurs clairement associées -->
<form @submit.prevent="submitGoodForm" novalidate>
  <h4>Inscription newsletter</h4>

  <!-- Résumé des erreurs avec navigation -->
  <div
    v-if="submitted && goodFormErrors.length"
    role="alert"
    aria-labelledby="error-summary-title">
    <h5 id="error-summary-title">Erreurs à corriger :</h5>
    <ul>
      <li v-for="error in goodFormErrors" :key="error.field">
        <a :href="`#${error.field}-good-form`">
          
        </a>
      </li>
    </ul>
  </div>

  <!-- Champ avec erreur spécifique associée -->
  <label for="name-good-form">Nom complet *</label>
  <input
    type="text"
    id="name-good-form"
    v-model="goodForm.name"
    required
    aria-invalid="submitted && !goodForm.name"
    aria-describedby="name-error">
  <div
    v-if="submitted && !goodForm.name"
    id="name-error"
    role="alert">
    ❌ Le nom est obligatoire
  </div>

  <button type="submit">S'inscrire</button>
</form>

Explication : Les erreurs doivent être clairement annoncées, associées aux champs concernés, et donner des instructions précises pour les corriger.

Groupes de champs avec aria-labelledby

Mauvais exemple

Préférences

<!-- ❌ Mauvais : Structure désorganisée -->
<form>
  <h4>Préférences</h4>

  <!-- Éléments mélangés sans logique -->
  <div>
    <input type="radio" name="contact-bad"
           id="email-contact-bad" value="email">
    <label for="email-contact-bad">Email</label>

    <input type="checkbox" id="newsletter-bad"
           value="newsletter">
    <label for="newsletter-bad">Newsletter</label>
  </div>

  <div>
    <input type="radio" name="contact-bad"
           id="phone-contact-bad" value="phone">
    <label for="phone-contact-bad">Téléphone</label>

    <!-- Champ texte sans contexte -->
    <input type="text" placeholder="Nom">
  </div>

  <div>
    <input type="checkbox" id="offers-bad" value="offers">
    <label for="offers-bad">Offres</label>

    <input type="radio" name="contact-bad"
           id="sms-contact-bad" value="sms">
    <label for="sms-contact-bad">SMS</label>
  </div>

  <!-- Select sans contexte -->
  <select>
    <option>Choisir...</option>
    <option>Quotidien</option>
    <option>Hebdomadaire</option>
  </select>

  <button type="submit">OK</button>
</form>

Bon exemple

Préférences de contact

Informations personnelles
Votre nom complet tel qu'il apparaîtra dans nos communications
Nous utiliserons cet email pour toutes nos communications
Méthode de contact préférée *
Types de communications

Sélectionnez les types de communications que vous souhaitez recevoir

Fréquence des communications
Définit la fréquence maximale de nos envois
<!-- ✅ Bon : Groupes logiques avec aria-labelledby -->
<form>
  <h4>Préférences de contact</h4>

  <!-- Section informations personnelles -->
  <div role="group" aria-labelledby="personal-info-title">
    <h5 id="personal-info-title">
      Informations personnelles
    </h5>

    <label for="name-good">Nom complet *</label>
    <input
      type="text"
      id="name-good"
      name="name"
      required
      aria-required="true"
      aria-describedby="name-desc">
    <span id="name-desc">
      Votre nom complet tel qu'il apparaîtra dans nos communications
    </span>
  </div>

  <!-- Groupe de boutons radio -->
  <div
    role="radiogroup"
    aria-labelledby="contact-method-title"
    aria-required="true">
    <h5 id="contact-method-title">
      Méthode de contact préférée *
    </h5>

    <input
      type="radio"
      name="contact-good"
      id="email-contact-good"
      value="email"
      required>
    <label for="email-contact-good">Email</label>

    <input
      type="radio"
      name="contact-good"
      id="phone-contact-good"
      value="phone"
      required>
    <label for="phone-contact-good">Téléphone</label>
  </div>

  <!-- Groupe de cases à cocher -->
  <div role="group" aria-labelledby="comm-types-title">
    <h5 id="comm-types-title">Types de communications</h5>
    <p id="comm-types-desc">
      Sélectionnez les types de communications que vous souhaitez recevoir
    </p>

    <input
      type="checkbox"
      id="newsletter-good"
      value="newsletter"
      aria-describedby="comm-types-desc">
    <label for="newsletter-good">Newsletter</label>
  </div>

  <button type="submit">
    Sauvegarder les préférences
  </button>
</form>

Explication : Les groupes de champs liés doivent être associés à leurs titres via aria-labelledby pour créer des relations sémantiques claires, particulièrement pour les boutons radio et cases à cocher.

Formulaire complexe avec validation temps réel

Mauvais exemple

Créer un compte

<!-- ❌ Mauvais : Validation intrusive -->
<form>
  <h4>Créer un compte</h4>

  <!-- Validation immédiate sur chaque frappe -->
  <label>Nom d'utilisateur</label>
  <input
    type="text"
    v-model="complexBadForm.username"
    @input="validateUsernameBad"
    :class="{ 'error': usernameBadError }">
  <!-- Erreur qui apparaît/disparaît constamment -->
  <div v-if="usernameBadError">
    
  </div>

  <!-- Pas d'association avec aria-describedby -->
  <label>Email</label>
  <input
    type="email"
    v-model="complexBadForm.email"
    @input="validateEmailBad"
    :class="{ 'error': emailBadError }">
  <div v-if="emailBadError"></div>

  <!-- Messages d'erreur trop courts et peu utiles -->
  <!-- Pas d'autocomplete pour aider les gestionnaires de mots de passe -->
  <label>Mot de passe</label>
  <input
    type="password"
    v-model="complexBadForm.password"
    @input="validatePasswordBad"
    :class="{ 'error': passwordBadError }">
  <div v-if="passwordBadError"></div>

  <button type="submit">Créer le compte</button>
</form>

Bon exemple

Créer un compte

Exigences :
  • Au moins 3 caractères
  • Lettres, chiffres et underscore uniquement
Force du mot de passe :
8+ caractères
Majuscule
Chiffre
Caractère spécial
<!-- ✅ Bon : Validation respectueuse avec feedback -->
<form>
  <h4>Créer un compte</h4>

  <!-- Validation sur blur, pas sur input -->
  <label for="username-complex">Nom d'utilisateur *</label>
  <input
    type="text"
    id="username-complex"
    v-model="complexGoodForm.username"
    @blur="validateUsernameGood"
    :class="{ error: usernameGoodError,
              success: usernameGoodValid }"
    aria-describedby="username-requirements username-feedback"
    aria-invalid="!!usernameGoodError"
    required>

  <!-- Instructions claires -->
  <div id="username-requirements">
    <h6>Exigences :</h6>
    <ul>
      <li :class="{ valid: complexGoodForm.username.length >= 3 }">
        Au moins 3 caractères
      </li>
      <li :class="{ valid: /^[a-zA-Z0-9_]+$/.test(username) }">
        Lettres, chiffres et underscore uniquement
      </li>
    </ul>
  </div>

  <!-- Feedback accessible -->
  <div
    v-if="usernameGoodError"
    id="username-feedback"
    role="alert"
    aria-live="polite">
    ❌ 
  </div>
  <div
    v-else-if="usernameGoodValid"
    id="username-feedback"
    role="status"
    aria-live="polite">
    ✅ Nom d'utilisateur disponible
  </div>

  <!-- Indicateur de force du mot de passe -->
  <label for="password-complex">Mot de passe *</label>
  <input
    type="password"
    id="password-complex"
    v-model="complexGoodForm.password"
    @input="validatePasswordGood"
    aria-describedby="password-strength"
    autocomplete="new-password"
    required>

  <div id="password-strength">
    <h6>Force du mot de passe :</h6>
    <div class="strength-indicator">
      <div
        class="strength-bar"
        :class="`strength-${passwordStrength}`"
        :style="{ width: `${passwordStrength * 25}%` }">
      </div>
    </div>
  </div>

  <button type="submit" :disabled="!isFormValid">
    Créer le compte
  </button>
</form>

Explication : Pour les formulaires complexes, la validation temps réel peut aider, mais elle doit être implémentée de manière non intrusive et accessible.