Formulaires Accessibles
Créer des formulaires utilisables par tous avec une expérience claire et guidée
Labels et associations
Mauvais exemple
<!-- ❌ 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
<!-- ✅ 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
<!-- ❌ 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
<!-- ✅ 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
<!-- ❌ 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
<!-- ✅ 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
<!-- ❌ 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
<!-- ✅ 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.