Cours HTML et CSS · Module 10: Accessibilité (a11y)

Les formulaires accessibles

7 min de lecture
Dans cette leçon7

Imagine que tu remplis un formulaire de réservation les yeux fermés, pendant que le lecteur d'écran se contente de dire « champ d'édition, vide » à chaque champ. Tu ne sais pas s'il faut saisir ton nom, ton e-mail ou la date de ta visite. C'est ainsi que sonne un formulaire sans étiquettes. Les formulaires comptent parmi les éléments interactifs les plus importants des sites web. Tout comme les scribes de l'Égypte antique devaient remplir des documents de papyrus précis, les utilisateurs doivent être guidés à travers les formulaires de manière claire et intuitive - en particulier ceux qui utilisent des technologies d'assistance.

Les étiquettes (labels)

Chaque champ de formulaire doit avoir une étiquette associée. C'est le fondement absolu de l'accessibilité des formulaires - l'étiquette, c'est l'inscription gravée sur le sceau, qui dit ce qu'il faut y saisir.

L'association explicite

Le moyen le plus sûr consiste à relier l'attribut for de l'étiquette à l'attribut id du champ. Les valeurs doivent être identiques, et l'id doit être unique sur toute la page :

1<!-- Meilleure méthode - for/id -->
2<label for="name">Nom complet :</label>
3<input type="text" id="name" name="name">
4
5<!-- Le lecteur d'écran annoncera : "Nom complet, champ texte" -->

Le lecteur annonce l'étiquette avec le type du champ, et un clic sur le libellé place le curseur dans le champ - une cible plus grande rend service à tout le monde, surtout sur un téléphone. Le champ lui-même n'a changé ni d'aspect ni de comportement : il a seulement gagné un nom.

L'association implicite

La deuxième façon consiste à placer le champ à l'intérieur de l'élément <label> - les attributs for et id ne sont alors plus nécessaires :

1<!-- Input à l'intérieur du label -->
2<label>
3    Adresse e-mail :
4    <input type="email" name="email">
5</label>

Cette association est correcte elle aussi, mais par défaut je te recommande l'association explicite avec for et id : elle fonctionne quelle que soit la disposition des éléments dans le HTML, et elle se vérifie facilement dans les DevTools.

Ce qu'il ne faut JAMAIS faire

On voit ces deux erreurs sur de nombreux sites, alors qu'elles ont l'air parfaitement inoffensives :

1<!-- MAUVAIS - pas de label -->
2<input type="text" placeholder="Saisis ton nom">
3<!-- Le placeholder DISPARAÎT après la saisie et n'est PAS une étiquette ! -->
4
5<!-- MAUVAIS - texte à côté mais non associé -->
6<span>Nom :</span>
7<input type="text">
8<!-- Le lecteur ne sait pas que "Nom :" est l'étiquette de ce champ -->

Le placeholder disparaît dès la première lettre, si bien que l'utilisateur perd l'indication en plein milieu de la saisie, et le texte d'un <span> n'est pour le lecteur qu'un texte ordinaire, relié à rien du tout.

Fieldset et Legend

Regroupe les champs liés à l'aide de <fieldset> et <legend>. La légende est le titre de tout le groupe, et le lecteur l'associe aux champs qu'il contient :

1<fieldset>
2    <legend>Informations personnelles</legend>
3
4    <label for="first-name">Prénom :</label>
5    <input type="text" id="first-name" name="firstName">
6
7    <label for="last-name">Nom :</label>
8    <input type="text" id="last-name" name="lastName">
9</fieldset>
10
11<fieldset>
12    <legend>Choisis une exposition :</legend>
13
14    <label>
15        <input type="radio" name="exhibition" value="pharaohs">
16        Les trésors des pharaons
17    </label>
18
19    <label>
20        <input type="radio" name="exhibition" value="mummies">
21        Les momies royales
22    </label>
23
24    <label>
25        <input type="radio" name="exhibition" value="jewelry">
26        Les bijoux de l'Égypte antique
27    </label>
28</fieldset>

<fieldset> est particulièrement important pour les groupes de boutons radio et de cases à cocher - sans lui, le lecteur ne connaît pas le contexte de la question. L'utilisateur entendrait « Les trésors des pharaons, bouton radio » sans savoir qu'il s'agit d'une réponse à une question sur l'exposition. L'attribut name commun garantit, lui, qu'une seule option peut être cochée.

Les messages d'erreur

La gestion des erreurs doit être accessible - l'utilisateur doit savoir qu'une erreur s'est produite et comment la corriger.

Les erreurs en ligne (inline)

Tu relies un message à son champ avec trois attributs ARIA que tu connais déjà grâce à la leçon sur ARIA :

1<div class="form-group">
2    <label for="email">Adresse e-mail (obligatoire) :</label>
3    <input type="email" id="email" name="email"
4           aria-required="true"
5           aria-invalid="true"
6           aria-describedby="email-error">
7    <p id="email-error" class="error" role="alert">
8        Merci de saisir une adresse e-mail valide, par exemple jean@example.com
9    </p>
10</div>

aria-invalid="true" marque le champ comme invalide, et aria-describedby pointe vers l'id du message, si bien que le lecteur le lit juste après l'étiquette. role="alert" fait annoncer le message immédiatement, dès qu'il apparaît sur la page. Remarque la formulation : elle dit comment corriger l'erreur, et pas seulement que quelque chose ne va pas.

Le récapitulatif des erreurs

Dans un formulaire plus long, ajoute en haut un récapitulatif dans lequel chaque erreur est un lien qui mène à son champ :

1<div role="alert" aria-labelledby="error-summary-title">
2    <h2 id="error-summary-title">
3        Le formulaire contient 2 erreurs :
4    </h2>
5    <ul>
6        <li>
7            <a href="#email">Champ e-mail - saisis une adresse valide</a>
8        </li>
9        <li>
10            <a href="#phone">Champ téléphone - minimum 9 chiffres requis</a>
11        </li>
12    </ul>
13</div>

Un clic sur le lien #email emmène l'utilisateur directement au champ erroné, si bien qu'il n'a pas à le chercher avec Tab dans tout le formulaire.

Les indications et instructions

Montre les exigences d'un champ avant que l'utilisateur ne commette une erreur. Le même aria-describedby relie le champ à ses instructions :

1<label for="password">Mot de passe :</label>
2<input type="password" id="password" name="password"
3       aria-describedby="password-requirements">
4<div id="password-requirements">
5    <p>Le mot de passe doit contenir :</p>
6    <ul>
7        <li>Au moins 8 caractères</li>
8        <li>Une majuscule</li>
9        <li>Un chiffre</li>
10        <li>Un caractère spécial</li>
11    </ul>
12</div>

Le lecteur lit les exigences dès que le focus arrive dans le champ du mot de passe, si bien qu'une personne voyante et une personne aveugle reçoivent la même information au même moment.

Autocomplete

L'attribut autocomplete aide les navigateurs et les technologies d'assistance à remplir automatiquement le formulaire :

1<form>
2    <label for="full-name">Nom complet :</label>
3    <input type="text" id="full-name" name="name"
4           autocomplete="name">
5
6    <label for="user-email">E-mail :</label>
7    <input type="email" id="user-email" name="email"
8           autocomplete="email">
9
10    <label for="user-tel">Téléphone :</label>
11    <input type="tel" id="user-tel" name="phone"
12           autocomplete="tel">
13</form>

Des valeurs comme name, email ou tel proviennent du standard HTML. Les WCAG 2.1 ont même un critère à part (1.3.5, niveau AA) qui exige de marquer ainsi les champs qui recueillent des données sur l'utilisateur - les personnes ayant un handicap moteur s'épargnent ainsi énormément de saisie.

Exemple de formulaire complet

Pour finir, voici tous les éléments réunis - un formulaire de réservation d'une visite au musée :

1<form aria-labelledby="form-title" novalidate>
2    <h2 id="form-title">Réserve une visite au musée</h2>
3
4    <fieldset>
5        <legend>Informations personnelles</legend>
6
7        <div class="form-group">
8            <label for="visitor-name">Nom complet :</label>
9            <input type="text" id="visitor-name"
10                   autocomplete="name"
11                   aria-required="true">
12        </div>
13
14        <div class="form-group">
15            <label for="visitor-email">E-mail :</label>
16            <input type="email" id="visitor-email"
17                   autocomplete="email"
18                   aria-required="true"
19                   aria-describedby="email-hint">
20            <p id="email-hint" class="hint">
21                Nous enverrons la confirmation à cette adresse.
22            </p>
23        </div>
24    </fieldset>
25
26    <fieldset>
27        <legend>Détails de la visite</legend>
28
29        <div class="form-group">
30            <label for="visit-date">Date de la visite :</label>
31            <input type="date" id="visit-date"
32                   aria-required="true">
33        </div>
34
35        <div class="form-group">
36            <label for="visitors-count">Nombre de personnes :</label>
37            <input type="number" id="visitors-count"
38                   min="1" max="20" value="1">
39        </div>
40    </fieldset>
41
42    <button type="submit">Réserver une visite</button>
43</form>

Remarque l'attribut novalidate sur <form> : il désactive les bulles de validation intégrées au navigateur, parce que c'est le script de la page qui vérifie les champs, et aria-required="true" indique au lecteur quels champs sont obligatoires. Si tu n'écris pas ta propre validation, utilise tout simplement l'attribut required - le navigateur bloquera l'envoi d'un champ vide, et le lecteur l'annoncera tout de même comme obligatoire.

Les formulaires accessibles sont comme un document de papyrus bien conçu - des instructions claires, des champs lisibles et un retour immédiat aident chaque utilisateur à aller jusqu'au bout du formulaire. Dans la leçon sur les tests, tu vérifieras un tel formulaire avec un lecteur d'écran.

Pratique

Dans l'éditeur ci-dessous, tu trouveras un formulaire de réservation. Parcours-le avec Tab et vérifie que chaque champ a une étiquette et que le groupe d'options a sa légende :

Code de cette leçon : index.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Formulaires accessibles</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <h1>Formulaire de réservation de visite au musée</h1>
11
12    <form aria-labelledby="form-title" novalidate>
13        <h2 id="form-title">Réserve une visite</h2>
14
15        <fieldset>
16            <legend>Informations personnelles</legend>
17
18            <div class="form-group">
19                <label for="visitor-name">
20                    Nom complet :
21                    <span class="required" aria-hidden="true">*</span>
22                </label>
23                <input type="text" id="visitor-name"
24                       name="name"
25                       autocomplete="name"
26                       aria-required="true">
27            </div>
28
29            <div class="form-group">
30                <label for="visitor-email">
31                    Adresse e-mail :
32                    <span class="required" aria-hidden="true">*</span>
33                </label>
34                <input type="email" id="visitor-email"
35                       name="email"
36                       autocomplete="email"
37                       aria-required="true"
38                       aria-describedby="email-hint">
39                <p id="email-hint" class="hint">
40                    Nous enverrons la confirmation de réservation à cette adresse.
41                </p>
42            </div>
43
44            <div class="form-group">
45                <label for="visitor-tel">Téléphone :</label>
46                <input type="tel" id="visitor-tel"
47                       name="phone"
48                       autocomplete="tel">
49            </div>
50        </fieldset>
51
52        <fieldset>
53            <legend>Choisis une exposition</legend>
54
55            <label class="radio-label">
56                <input type="radio" name="exhibition"
57                       value="tutankhamun">
58                Les trésors de Toutânkhamon
59            </label>
60
61            <label class="radio-label">
62                <input type="radio" name="exhibition"
63                       value="mummies">
64                Les momies royales
65            </label>
66
67            <label class="radio-label">
68                <input type="radio" name="exhibition"
69                       value="jewelry">
70                Les bijoux de l'Égypte antique
71            </label>
72        </fieldset>
73
74        <fieldset>
75            <legend>Détails de la visite</legend>
76
77            <div class="form-group">
78                <label for="visit-date">
79                    Date de la visite :
80                    <span class="required" aria-hidden="true">*</span>
81                </label>
82                <input type="date" id="visit-date"
83                       name="date"
84                       aria-required="true">
85            </div>
86
87            <div class="form-group">
88                <label for="visitors-count">Nombre de personnes :</label>
89                <input type="number" id="visitors-count"
90                       name="count"
91                       min="1" max="20" value="1">
92            </div>
93
94            <div class="form-group">
95                <label>
96                    <input type="checkbox" name="guide">
97                    Je souhaite un guide pour l'exposition
98                </label>
99            </div>
100        </fieldset>
101
102        <p class="required-info">
103            <span class="required" aria-hidden="true">*</span>
104            Champs obligatoires
105        </p>
106
107        <button type="submit">Réserver une visite</button>
108    </form>
109</body>
110</html>

Souviens-toi : chaque champ est un sceau sur un papyrus - sans l'inscription de son <label>, personne ne sait ce qu'il faut y imprimer.

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quand faut-il utiliser les éléments <fieldset> et <legend> dans les formulaires ?

Tâches pratiques dans le jeu

  • Tri horizontal

    Compose l'association correcte d'un label avec un input :

  • Éditeur de code

    Crée un formulaire de réservation accessible avec fieldset/legend, label for/id, aria-required et aria-describedby. Ajoute un groupe de boutons radio avec fieldset/legend.

  • Clique dans l'ordre

    Compose la structure correcte d'un fieldset avec legend pour un groupe de boutons radio :

Articles utiles