Cours HTML et CSS · Module 10: Accessibilité (a11y)
Projet : Page accessible du musée égyptien
Dans cette leçon5
Tu connais déjà tous les éléments d'une page accessible, mais pour l'instant ils sont éparpillés, comme des blocs de pierre sur un chantier. La vraie épreuve commence quand il faut les assembler en une pyramide entière et vérifier que tous les couloirs se rejoignent. Il est temps de réunir tout ce que nous avons appris sur l'accessibilité dans un seul projet ! Nous allons créer une page du musée de l'Égypte antique qui sera entièrement accessible - conforme aux normes WCAG 2.1 au niveau AA.
Les éléments du projet
Notre page contiendra :
- Lien d'évitement - qui mène directement au contenu principal
- Structure sémantique - header, nav, main, aside, footer
- Navigation accessible - avec aria-label et aria-current
- Section des expositions - avec des images accessibles (alt, figure, figcaption)
- Formulaire de réservation - avec des labels, fieldset, legend et une validation
- Section multimédia - avec des sous-titres et une transcription
- Contraste et typographie - conformes aux WCAG AA
- Navigation au clavier - focus visible, ordre de Tab logique
- Attributs ARIA - aria-live, aria-expanded, aria-describedby
La structure HTML
Étape 1 : head, lien d'évitement et navigation
Nous commençons la construction par le haut. lang="fr" indique au lecteur d'écran dans quelle langue lire, et le lien d'évitement doit être le premier élément de <body>, pour que Tab l'atteigne en premier :
1<!DOCTYPE html>
2<html lang="fr">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Musée de l'Égypte antique - Page accessible</title>
7</head>
8<body>
9 <!-- 1. Lien d'évitement -->
10 <a href="#main-content" class="skip-link">
11 Aller au contenu principal
12 </a>
13
14 <!-- 2. En-tête avec navigation -->
15 <header role="banner">
16 <h1>Musée de l'Égypte antique</h1>
17 <nav aria-label="Navigation principale">
18 <ul>
19 <li><a href="#exhibitions" aria-current="page">Expositions</a></li>
20 <li><a href="#reservation">Réservation</a></li>
21 <li><a href="#multimedia">Multimédia</a></li>
22 <li><a href="#contact">Contact</a></li>
23 </ul>
24 </nav>
25 </header>L'attribut role="banner" est superflu ici, car un <header> placé au niveau supérieur de la page possède déjà ce rôle - dans ton projet, tu peux le supprimer. La navigation a reçu le nom « Navigation principale », car une seconde navigation apparaîtra dans le pied de page.
Étape 2 : la section des expositions
Le contenu principal reçoit id="main-content", c'est-à-dire la cible du lien d'évitement. La section des expositions a son propre titre, et aria-labelledby lui donne un nom tiré de ce titre :
1 <!-- 3. Contenu principal -->
2 <main id="main-content">
3 <!-- Section des expositions -->
4 <section aria-labelledby="exhibitions-title">
5 <h2 id="exhibitions-title">Expositions actuelles</h2>
6
7 <article>
8 <h3>Les trésors de Toutânkhamon</h3>
9 <figure>
10 <img src="tutankhamun-mask.jpg"
11 alt="Masque funéraire en or du pharaon Toutânkhamon">
12 <figcaption>
13 Masque de Toutânkhamon, XVIIIe dynastie, vers 1323 av. J.-C.
14 </figcaption>
15 </figure>
16 <p>Découvre des artefacts extraordinaires provenant de la tombe du jeune pharaon.</p>
17 </article>
18 </section>Grâce à son nom, la section devient un landmark « Expositions actuelles ». La hiérarchie des titres est continue : <h1> dans l'en-tête de la page, <h2> pour la section et <h3> pour l'exposition.
Étape 3 : le formulaire de réservation
Le formulaire réunit tout ce que tu as vu dans la leçon sur les formulaires - les étiquettes, les groupes de champs, les descriptions et la saisie automatique :
1 <!-- Formulaire de réservation -->
2 <section aria-labelledby="reservation-title">
3 <h2 id="reservation-title">Réserve une visite</h2>
4 <form aria-labelledby="reservation-title" novalidate>
5 <fieldset>
6 <legend>Informations personnelles</legend>
7 <div class="form-group">
8 <label for="name">Nom complet :</label>
9 <input type="text" id="name"
10 autocomplete="name"
11 aria-required="true">
12 </div>
13 <div class="form-group">
14 <label for="email">E-mail :</label>
15 <input type="email" id="email"
16 autocomplete="email"
17 aria-required="true"
18 aria-describedby="email-help">
19 <p id="email-help" class="hint">
20 Nous enverrons la confirmation de réservation à cette adresse.
21 </p>
22 </div>
23 </fieldset>
24
25 <fieldset>
26 <legend>Choisis une exposition</legend>
27 <label>
28 <input type="radio" name="exhibition"
29 value="tutankhamun">
30 Les trésors de Toutânkhamon
31 </label>
32 <label>
33 <input type="radio" name="exhibition"
34 value="mummies">
35 Les momies royales
36 </label>
37 </fieldset>
38
39 <button type="submit">Réserver</button>
40 </form>
41 </section>
42 </main>Chaque champ a un label avec le for correspondant, le groupe de boutons radio a son propre <fieldset> avec une <legend>, et l'indication sous l'e-mail est reliée au champ par aria-describedby.
Étape 4 : la barre latérale et le pied de page
Pour finir, les éléments situés en dehors du contenu principal - les horaires d'ouverture dans un <aside> et un pied de page avec une seconde navigation :
1 <!-- 4. Barre latérale -->
2 <aside aria-label="Informations complémentaires">
3 <h2>Horaires d'ouverture</h2>
4 <dl>
5 <dt>Lundi - Vendredi</dt>
6 <dd>9h00 - 17h00</dd>
7 <dt>Samedi - Dimanche</dt>
8 <dd>10h00 - 18h00</dd>
9 </dl>
10 </aside>
11
12 <!-- 5. Pied de page -->
13 <footer>
14 <p>Musée de l'Égypte antique 2024</p>
15 <nav aria-label="Navigation du pied de page">
16 <ul>
17 <li><a href="/privacy-policy">Politique de confidentialité</a></li>
18 <li><a href="/accessibility">Déclaration d'accessibilité</a></li>
19 </ul>
20 </nav>
21 </footer>
22</body>
23</html>Les horaires d'ouverture forment une liste de définitions, car ils associent un jour à des heures. La seconde navigation porte un autre nom, si bien que le lecteur peut la distinguer de la navigation principale, et un lien vers la déclaration d'accessibilité est une bonne habitude - en Pologne comme en France, les organismes publics sont tenus de la publier.
Étape 5 : les messages dynamiques
Les exigences comprennent aussi des attributs ARIA pour le contenu qui change sur la page. Ajoute une région pour le message affiché après la réservation et un bouton qui déplie les horaires d'ouverture :
1<!-- Message après l'envoi du formulaire -->
2<p id="form-status" aria-live="polite"></p>
3
4<!-- Bouton qui déplie des informations supplémentaires -->
5<button type="button" aria-expanded="false" aria-controls="hours">
6 Afficher les horaires d'ouverture
7</button>La région aria-live est présente sur la page dès le départ, et elle est vide - le lecteur n'annonce un texte qu'au moment où un script l'y écrit. Le script fait aussi passer aria-expanded à "true" une fois l'élément déplié, et aria-controls pointe vers l'id de l'élément qui se déplie.
Les styles CSS - l'accessibilité
Étape 6 : les couleurs, le lien d'évitement et le focus
En CSS, nous commençons par des variables qui contiennent des couleurs à fort contraste, puis nous cachons le lien d'évitement et concevons un contour de focus bien visible :
1/* Variables de couleur à contraste élevé */
2:root {
3 --text-primary: #1a1a2e;
4 --text-secondary: #4a4a5a;
5 --bg-primary: #ffffff;
6 --bg-secondary: #f5f1e3;
7 --accent-gold: #8b6914;
8 --accent-hover: #6b4c00;
9 --error: #d32f2f;
10 --success: #2e7d32;
11 --focus-color: #d4af37;
12}
13
14/* Lien d'évitement */
15.skip-link {
16 position: absolute;
17 top: -50px;
18 left: 16px;
19 background: var(--text-primary);
20 color: var(--bg-primary);
21 padding: 12px 24px;
22 z-index: 1000;
23 font-size: 1rem;
24 border-radius: 0 0 4px 4px;
25 transition: top 0.2s;
26}
27
28.skip-link:focus {
29 top: 0;
30}
31
32/* Focus visible pour tous les éléments interactifs */
33a:focus-visible,
34button:focus-visible,
35input:focus-visible,
36select:focus-visible,
37textarea:focus-visible {
38 outline: 3px solid var(--focus-color);
39 outline-offset: 2px;
40}Vérifie toutefois une chose par toi-même : le contour doré #d4af37 n'atteint qu'environ 2,1:1 sur fond blanc, alors que les WCAG exigent au moins 3:1 pour les états des composants d'interface. Sur un fond clair, le --accent-gold plus foncé (environ 5:1) fonctionne mieux - la preuve qu'un code tout prêt mérite toujours d'être testé.
Étape 7 : la typographie et les préférences de l'utilisateur
La dernière partie de la feuille de style met en place une typographie lisible et réagit à trois réglages du système : la réduction des animations, le contraste renforcé et le mode sombre :
1/* Typographie accessible */
2body {
3 font-family: Georgia, 'Times New Roman', serif;
4 font-size: 100%;
5 line-height: 1.6;
6 color: var(--text-primary);
7 background: var(--bg-primary);
8}
9
10/* Largeur de texte de 50 à 80 caractères */
11main {
12 max-width: 70ch;
13 margin: 0 auto;
14 padding: 1rem;
15}
16
17/* Prefers reduced motion */
18@media (prefers-reduced-motion: reduce) {
19 * {
20 animation-duration: 0.01ms !important;
21 transition-duration: 0.01ms !important;
22 }
23}
24
25/* Prefers contrast */
26@media (prefers-contrast: more) {
27 :root {
28 --text-primary: #000000;
29 --bg-primary: #ffffff;
30 --accent-gold: #6b4c00;
31 }
32}
33
34/* Mode sombre */
35@media (prefers-color-scheme: dark) {
36 :root {
37 --text-primary: #e0d5c0;
38 --bg-primary: #1a1a2e;
39 --bg-secondary: #2d2d44;
40 }
41}Les media queries ne font que remplacer les valeurs des variables, si bien que le reste de la feuille de style reste intact. Une taille de police en pourcentage et une colonne de 70ch permettent à la page de bien supporter un zoom à 200 %.
La checklist du projet
Une fois le projet terminé, vérifie :
- Le lien d'évitement fonctionne et devient visible au focus
- Structure sémantique (header, nav, main, aside, footer)
- Navigation avec aria-label et aria-current
- Toutes les images ont un texte alternatif
- Le formulaire a un label pour chaque champ
- Fieldset et legend pour les groupes de champs
- aria-required et aria-describedby dans le formulaire
- Contraste des couleurs d'au moins 4.5:1
- Focus visible sur tous les éléments
- La page fonctionne avec un zoom à 200 %
- prefers-reduced-motion pris en charge
- prefers-contrast pris en charge
- prefers-color-scheme pris en charge
- aria-live pour les messages et aria-expanded pour les éléments dépliables
Tu vérifieras chaque point avec les outils de la leçon sur les tests : Lighthouse et axe pour commencer, puis le clavier, le zoom et un lecteur d'écran. Envoie le lien vers ta page avec une courte description des techniques que tu as utilisées.
Félicitations ! Tu as créé une page vraiment accessible à tous - tout comme les temples de l'Égypte antique étaient ouverts à chaque pèlerin. Mohamed serait fier de toi ! Dans le prochain module, tu découvriras des outils qui accéléreront ton travail - des DevTools jusqu'à la publication d'une page sur Internet.
Pratique
Dans l'éditeur ci-dessous, tu trouveras une version complète du projet. Parcours-la, checklist en main, avant de construire la tienne :
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>Musée de l'Égypte antique - Page accessible</title>
7 <link rel="stylesheet" href="style.css">
8</head>
9<body>
10 <!-- Lien d'évitement -->
11 <a href="#main-content" class="skip-link">
12 Aller au contenu principal
13 </a>
14
15 <!-- En-tête -->
16 <header role="banner">
17 <div class="header-inner">
18 <h1>
19 <span aria-hidden="true">▵</span>
20 Musée de l'Égypte antique
21 </h1>
22 <nav aria-label="Navigation principale">
23 <ul>
24 <li><a href="#exhibitions" aria-current="page">Expositions</a></li>
25 <li><a href="#reservation">Réservation</a></li>
26 <li><a href="#gallery">Galerie</a></li>
27 <li><a href="#contact">Contact</a></li>
28 </ul>
29 </nav>
30 </div>
31 </header>
32
33 <!-- Contenu principal -->
34 <main id="main-content">
35 <!-- Section des expositions -->
36 <section id="exhibitions" aria-labelledby="ex-title">
37 <h2 id="ex-title">Expositions actuelles</h2>
38
39 <article>
40 <h3>Les trésors de Toutânkhamon</h3>
41 <figure>
42 <img src="https://placehold.co/600x350/d4af37/1a1a2e?text=Mask+Tutankhamun"
43 alt="Masque funéraire en or du pharaon Toutânkhamon avec des incrustations de lapis-lazuli">
44 <figcaption>
45 Masque funéraire de Toutânkhamon, XVIIIe dynastie, vers 1323 av. J.-C.
46 </figcaption>
47 </figure>
48 <p>Découvre plus de 5000 artefacts extraordinaires provenant de la tombe
49 du jeune pharaon, découverte en 1922.</p>
50 <a href="#reservation" class="btn">Réserve une visite</a>
51 </article>
52
53 <article>
54 <h3>Les momies royales</h3>
55 <p>Découvre les secrets de la momification et observe les momies
56 préservées des pharaons de la Vallée des Rois.</p>
57 <a href="#reservation" class="btn">Réserve une visite</a>
58 </article>
59 </section>
60
61 <!-- Formulaire de réservation -->
62 <section id="reservation" aria-labelledby="res-title">
63 <h2 id="res-title">Réserve une visite</h2>
64
65 <form aria-labelledby="res-title" novalidate>
66 <fieldset>
67 <legend>Informations personnelles</legend>
68
69 <div class="form-group">
70 <label for="name">
71 Nom complet
72 <span class="required" aria-hidden="true">*</span>
73 </label>
74 <input type="text" id="name"
75 autocomplete="name"
76 aria-required="true">
77 </div>
78
79 <div class="form-group">
80 <label for="email">
81 Adresse e-mail
82 <span class="required" aria-hidden="true">*</span>
83 </label>
84 <input type="email" id="email"
85 autocomplete="email"
86 aria-required="true"
87 aria-describedby="email-help">
88 <p id="email-help" class="hint">
89 Nous enverrons la confirmation à cette adresse.
90 </p>
91 </div>
92 </fieldset>
93
94 <fieldset>
95 <legend>Choisis une exposition</legend>
96 <label class="radio-label">
97 <input type="radio" name="exhibition" value="tutankhamun">
98 Les trésors de Toutânkhamon
99 </label>
100 <label class="radio-label">
101 <input type="radio" name="exhibition" value="mummies">
102 Les momies royales
103 </label>
104 </fieldset>
105
106 <fieldset>
107 <legend>Détails de la visite</legend>
108 <div class="form-row">
109 <div class="form-group">
110 <label for="visit-date">Date de la visite :</label>
111 <input type="date" id="visit-date" aria-required="true">
112 </div>
113 <div class="form-group">
114 <label for="visitors">Nombre de personnes :</label>
115 <input type="number" id="visitors" min="1" max="20" value="1">
116 </div>
117 </div>
118 </fieldset>
119
120 <button type="submit" class="btn btn-primary">
121 Réserver une visite
122 </button>
123 </form>
124 </section>
125 </main>
126
127 <!-- Barre latérale -->
128 <aside aria-label="Informations pratiques">
129 <h2>Horaires d'ouverture</h2>
130 <dl>
131 <dt>Lundi - Vendredi</dt>
132 <dd>9h00 - 17h00</dd>
133 <dt>Samedi - Dimanche</dt>
134 <dd>10h00 - 18h00</dd>
135 </dl>
136
137 <h2>Tarifs</h2>
138 <dl>
139 <dt>Billet normal</dt>
140 <dd>45 €</dd>
141 <dt>Billet réduit</dt>
142 <dd>25 €</dd>
143 <dt>Billet famille</dt>
144 <dd>100 € (2+2)</dd>
145 </dl>
146 </aside>
147
148 <!-- Pied de page -->
149 <footer>
150 <nav aria-label="Navigation du pied de page">
151 <ul>
152 <li><a href="/privacy">Politique de confidentialité</a></li>
153 <li><a href="/accessibility">Déclaration d'accessibilité</a></li>
154 <li><a href="/sitemap">Plan du site</a></li>
155 </ul>
156 </nav>
157 <p>Musée de l'Égypte antique © 2024. Tous droits réservés.</p>
158 </footer>
159</body>
160</html>Souviens-toi : l'accessibilité n'est pas une décoration ajoutée à la fin, mais le fondement de la pyramide - construis-la dès le premier bloc, et chaque pèlerin atteindra la chambre du pharaon.
Tu as repéré une erreur dans cette leçon ?