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

Le contraste des couleurs et la typographie

9 min de lecture
Dans cette leçon8

Tu ouvres la page du musée sur ton téléphone, en plein soleil au bord du Nil, et tu découvres un texte gris clair sur fond blanc, impossible à lire. Une personne malvoyante la voit toujours ainsi. Dans l'Égypte antique, les hiéroglyphes étaient peints de couleurs vives sur des fonds contrastés - non pas pour décorer, mais pour qu'on puisse les lire même dans les chambres sombres des temples. Aujourd'hui, le même principe s'applique au web design : le contenu doit être lisible pour tout le monde.

Le ratio de contraste

Le ratio de contraste compare la luminosité de deux couleurs et va de 1:1 (la même couleur) à 21:1 (du noir sur du blanc). Les WCAG définissent des exigences minimales de contraste entre le texte et l'arrière-plan :

NiveauTexte normalGrand texte (24px ou environ 18,7px en gras)
AA4.5:13:1
AAA7:14.5:1

Selon les WCAG, un grand texte mesure au moins 18 points (environ 24px) ou 14 points en gras. Pour un texte normal au niveau AA, retiens un seul nombre : 4.5:1.

Exemples de contraste

Regarde ces trois paires de couleurs - une bonne, une mauvaise et une corrigée. Tu peux vérifier les valeurs indiquées dans les commentaires avec n'importe quel outil de contraste :

1/* BON - contraste élevé */
2.good-contrast {
3    color: #1a1a2e;            /* Texte foncé */
4    background-color: #f5f1e3; /* Fond clair */
5    /* Contraste : ~15:1 - respecte le niveau AAA */
6}
7
8/* MAUVAIS - contraste trop faible */
9.bad-contrast {
10    color: #999999;            /* Texte gris */
11    background-color: #cccccc; /* Fond gris */
12    /* Contraste : ~1.8:1 - ne respecte même pas le niveau AA */
13}
14
15/* CORRIGÉ */
16.fixed-contrast {
17    color: #595959;            /* Gris plus foncé */
18    background-color: #ffffff; /* Fond blanc */
19    /* Contraste : ~7:1 - respecte le niveau AA, et même AAA */
20}

La correction n'a pas changé le caractère de la page - il a suffi d'assombrir le texte et d'éclaircir le fond. Le contraste dépend des deux couleurs, alors vérifie toujours la paire entière.

Ne compte pas uniquement sur la couleur

La couleur ne peut pas être le seul moyen de transmettre une information - les personnes daltoniennes peuvent ne pas distinguer certaines couleurs, en particulier le rouge et le vert. Compare un champ dont l'erreur n'est signalée que par la couleur avec un champ qui le dit de trois façons :

1<!-- MAUVAIS - seule la couleur indique l'erreur -->
2<style>
3    .error-field { border-color: red; }
4</style>
5<input type="email" class="error-field">
6
7<!-- BON - couleur + icône + texte -->
8<style>
9    .error-field {
10        border: 2px solid #d32f2f;
11        background-image: url('error-icon.svg');
12        background-repeat: no-repeat;
13        background-position: right 8px center;
14    }
15</style>
16<input type="email" class="error-field"
17       aria-invalid="true"
18       aria-describedby="email-err">
19<p id="email-err" class="error-msg">
20    <span aria-hidden="true">&#10060;</span>
21    Merci de saisir une adresse e-mail valide
22</p>

La bordure rouge reste, mais s'y ajoutent une icône et un texte relié au champ par aria-describedby. La croix porte aria-hidden="true", car le lecteur lira de toute façon le message qui l'accompagne.

La couleur n'atteint pas non plus le lecteur d'écran : quand le statut des billets passe du vert au rouge, une personne aveugle ne remarque rien - c'est pourquoi le changement est annoncé par une région aria-live, que tu connais grâce à la leçon sur ARIA :

1<div aria-live="polite" id="ticket-status">
2    Il ne reste que 3 billets pour aujourd'hui
3</div>

La valeur polite attend que le lecteur ait terminé sa phrase en cours, assertive l'interrompt immédiatement, et off (la valeur par défaut quand l'attribut est absent) n'annonce rien. role="alert" fonctionne comme un raccourci pour aria-live="assertive" - réserve-le aux messages urgents.

La typographie accessible

La taille du texte

Définis la taille du texte en unités relatives. rem se calcule à partir de la taille de police de l'élément html, qui vaut 16px par défaut et dépend des réglages du navigateur de l'utilisateur :

1/* Utilise des unités relatives */
2body {
3    font-size: 100%; /* = 16px par défaut */
4}
5
6h1 { font-size: 2rem; }      /* 32px */
7h2 { font-size: 1.5rem; }    /* 24px */
8p  { font-size: 1rem; }      /* 16px */
9small { font-size: 0.875rem; } /* 14px - minimum ! */
10
11/* NE bloque JAMAIS le zoom du texte */
12/* MAUVAIS */
13html { font-size: 16px !important; }
14/* BON */
15html { font-size: 100%; }

Quand quelqu'un choisit une police plus grande dans son navigateur, une page en rem grandit avec elle, alors qu'un 16px !important rigide ignore son choix. Les WCAG n'imposent pas de taille de police minimale, mais 16px pour le texte courant est une norme raisonnable.

L'interligne et les espacements

Le critère WCAG 1.4.12 (niveau AA) exige que le contenu ne soit ni coupé ni superposé quand l'utilisateur augmente les espacements jusqu'aux valeurs ci-dessous. Le plus simple est de prévoir cette marge dès la conception :

1/* Valeurs du critère WCAG 1.4.12 : */
2p {
3    line-height: 1.5;        /* Hauteur de ligne : 1,5x la taille de police */
4    letter-spacing: 0.12em;  /* Espacement des lettres */
5    word-spacing: 0.16em;    /* Espacement des mots */
6}
7
8/* Espacement des paragraphes >= 2x la taille de police */
9p + p {
10    margin-top: 2em;
11}

C'est un seuil de robustesse, et non une règle rigide - dans les exercices, tu rencontreras aussi un espacement net, bien que plus petit, de margin-top: 1.5em.

La largeur du texte

Les lignes très longues fatiguent les yeux, car il est difficile de retrouver le début de la ligne suivante. L'unité ch correspond à la largeur du caractère « 0 » dans la police courante :

1/* Largeur de ligne optimale : 50 à 80 caractères */
2.article-content {
3    max-width: 70ch; /* 70 caractères */
4    margin: 0 auto;
5}

La colonne contient donc environ 70 caractères, et margin: 0 auto la centre. Au niveau AAA (critère 1.4.8), les WCAG parlent d'un maximum de 80 caractères par ligne.

Les outils de vérification du contraste

La spécification CSS décrit des fonctions qui choisissent elles-mêmes une couleur contrastée : la fonction expérimentale color-contrast() et la plus simple contrast-color(), mais leur prise en charge par les navigateurs reste limitée. Tu vérifieras donc le contraste dans le sélecteur de couleurs des Chrome DevTools ou dans le WebAIM Contrast Checker. Les couleurs se gèrent le plus facilement dans des variables CSS, que les media queries remplacent selon le mode :

1/* Utilise des variables CSS pour la cohérence */
2:root {
3    --text-primary: #1a1a2e;
4    --text-secondary: #4a4a5a;
5    --bg-primary: #ffffff;
6    --bg-secondary: #f5f1e3;
7    --accent: #8b6914;
8    --error: #d32f2f;
9    --success: #2e7d32;
10}
11
12/* Mode contraste élevé */
13@media (prefers-contrast: more) {
14    :root {
15        --text-primary: #000000;
16        --text-secondary: #1a1a1a;
17        --bg-primary: #ffffff;
18        --accent: #6b4c00;
19    }
20}
21
22/* Mode sombre */
23@media (prefers-color-scheme: dark) {
24    :root {
25        --text-primary: #e0e0e0;
26        --text-secondary: #b0b0b0;
27        --bg-primary: #1a1a2e;
28        --bg-secondary: #2d2d44;
29    }
30}

Le mode sombre n'oblige pas à réécrire les sélecteurs - seules les valeurs dans :root changent. Revérifie tout de même chaque nouvelle paire de couleurs, car un texte clair sur fond sombre a lui aussi besoin de 4.5:1. Des documents plus anciens utilisent la valeur high, mais les navigateurs reconnaissent prefers-contrast: more.

Prefers-contrast et forced-colors

Deux autres media queries réagissent aux réglages du système : prefers-contrast détecte une demande de contraste renforcé, et forced-colors un mode dans lequel le système impose sa propre palette, comme les thèmes de contraste de Windows :

1/* Mode contraste élevé */
2@media (prefers-contrast: more) {
3    button {
4        border: 3px solid currentColor;
5    }
6}
7
8/* Mode couleurs forcées (par exemple, le contraste élevé de Windows) */
9@media (forced-colors: active) {
10    .card {
11        border: 2px solid ButtonText;
12    }
13
14    a {
15        color: LinkText;
16    }
17
18    /* Les arrière-plans décoratifs sont supprimés - ajoute des bordures */
19    .highlight {
20        forced-color-adjust: none;
21        /* OU conserve les couleurs d'origine */
22    }
23}

Dans ce mode, le navigateur remplace les couleurs du texte, du fond et des bordures par la palette du système, si bien qu'un élément mis en valeur uniquement par son arrière-plan peut disparaître - une bordure, elle, survivra. ButtonText et LinkText sont des couleurs système, et forced-color-adjust: none désactive le forçage - utilise-le rarement.

Le contraste du contour de focus

Le contraste ne concerne pas que le texte. Le critère WCAG 1.4.11 (niveau AA) exige au moins 3:1 pour les composants d'interface et leurs états, y compris le contour :focus-visible de la leçon sur la navigation au clavier. L'or #d4af37 ressort magnifiquement sur un bleu marine foncé (environ 8:1), mais sur fond blanc il n'atteint qu'environ 2,1:1, alors dans ce cas, choisis une teinte plus foncée.

La checklist du contraste et de la typographie

  1. Contraste du texte par rapport au fond : minimum 4.5:1 (AA) ou 7:1 (AAA)
  2. Taille de police : minimum 16px (1rem) pour le body
  3. Hauteur de ligne : minimum 1.5
  4. Largeur de ligne : 50 à 80 caractères
  5. Ne compte pas uniquement sur la couleur
  6. Prends en charge prefers-contrast et prefers-color-scheme
  7. Utilise des unités relatives (rem, em)

Tout comme les anciens Égyptiens veillaient à la lisibilité des hiéroglyphes peints sur les murs des temples, nous devons veiller à la lisibilité du texte sur nos pages - pour que chacun puisse lire notre message, quelles que soient les conditions. Dans la prochaine leçon, tu vérifieras tout cela avec des outils.

Pratique

Compare dans l'éditeur ci-dessous les bonnes et les mauvaises paires de couleurs, puis modifie-les et vérifie le contraste dans les DevTools :

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>Contraste des couleurs et typographie</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <h1>Contraste et typographie</h1>
11
12    <section>
13        <h2>Exemples de contraste</h2>
14
15        <div class="contrast-box good-contrast">
16            <h3>Bon contraste (12:1)</h3>
17            <p>Texte foncé sur fond clair - lisible par tout le monde.</p>
18        </div>
19
20        <div class="contrast-box bad-contrast">
21            <h3>Mauvais contraste (1.8:1)</h3>
22            <p>Texte gris sur fond gris - presque illisible !</p>
23        </div>
24
25        <div class="contrast-box fixed-contrast">
26            <h3>Contraste corrigé (5.9:1)</h3>
27            <p>Gris plus foncé sur fond blanc - respecte WCAG AA.</p>
28        </div>
29    </section>
30
31    <section>
32        <h2>Ne te fie pas uniquement à la couleur</h2>
33
34        <div class="example-bad">
35            <h3>Mauvais : seule la couleur indique l'erreur</h3>
36            <input type="email" class="error-only-color" value="incorrect">
37        </div>
38
39        <div class="example-good">
40            <h3>Bon : couleur + icône + texte</h3>
41            <input type="email" class="error-with-icon"
42                   value="incorrect"
43                   aria-invalid="true"
44                   aria-describedby="email-err">
45            <p id="email-err" class="error-msg">
46                &#10060; Saisis une adresse e-mail valide
47            </p>
48        </div>
49    </section>
50
51    <section>
52        <h2>Typographie accessible</h2>
53        <article class="readable-text">
54            <h3>Texte correctement mis en forme</h3>
55            <p>Ce texte a une bonne hauteur de ligne (1.6), une taille de police (1rem = 16px)
56               et une largeur de ligne optimale (max. 70 caractères). Tous ces paramètres
57               sont importants pour la lisibilité et respectent les exigences des WCAG.</p>
58            <p>Le second paragraphe a un espacement suffisant par rapport au premier (2em),
59               ce qui facilite la lecture des textes plus longs.</p>
60        </article>
61    </section>
62
63    <section>
64        <h2>Variables CSS et thèmes</h2>
65        <div class="theme-demo">
66            <p>Ce texte utilise des variables CSS (Custom Properties) et s'adapte
67               automatiquement aux préférences de l'utilisateur : mode sombre,
68               contraste élevé.</p>
69        </div>
70    </section>
71</body>
72</html>

Souviens-toi : un hiéroglyphe peint en or sur du sable est magnifique, mais personne ne peut le lire - le contraste est la peinture qui permet à chaque pèlerin de voir le message.

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. Quel contraste minimum les WCAG 2.1 niveau AA exigent-elles pour le texte NORMAL ?

  2. 2. Quel est le ratio de contraste texte/arrière-plan minimum exigé par les WCAG AA pour le texte normal ?

Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Améliore le contraste des couleurs (rends le texte clair plus foncé), ajoute des icônes à côté des statuts colorés, et crée des variables CSS avec un mode sombre.

  • Éditeur de code

    Améliore la typographie : change font-size en 100%, règle line-height à 1.5, max-width à 70ch, et ajoute letter-spacing, word-spacing et margin-top pour les paragraphes.

  • Clique dans l'ordre

    Compose la syntaxe correcte de la media query CSS pour le mode sombre :

  • Tri vertical

    Compose la syntaxe CSS correcte d'un indicateur de focus visible :

  • Clique dans l'ordre

    Compose la syntaxe correcte d'un élément div avec une région aria-live :

  • Éditeur de code

    Ajoute aria-live="polite" à la région du statut des billets, role="alert" pour les notifications urgentes, et aria-live="assertive" au compte à rebours.

  • Tri vertical

    Range les valeurs d'aria-live de la priorité la plus basse à la plus haute :

Articles utiles