Bhinish Dhiman

Contraste en mode sombre : pourquoi WCAG 2 vous induit en erreur

Traduit de l’original en anglais. Lire l’original

WCAG 2 ajoute une constante fixe de 0.05 aux deux valeurs de luminance. Près du noir, cette constante domine le ratio : des paires en mode sombre peuvent obtenir une validation confortable tout en paraissant ternes. Vérifiez les thèmes sombres avec l'APCA, et attendez-vous à devoir créer une variante plus claire de chaque couleur de marque.

Une palette qui passe toutes les vérifications sur blanc ne passera pas automatiquement une fois inversée. Pire, l’outil standard vous dira souvent que si.

D’où vient le problème

La formule de contraste de WCAG 2 est la suivante :

(L1 + 0.05) / (L2 + 0.05)

où L1 et L2 sont les luminances relatives de la couleur la plus claire et de la plus sombre. Ce 0.05 modélise le reflet ambiant de l’écran (flare) : la lumière réfléchie par l’écran, qui ne disparaît jamais complètement, si bien que rien à l’écran n’a réellement une luminance nulle.

C’est une simplification raisonnable, et du côté clair de la plage elle ne pèse presque rien. Une luminance de 0.9 plus 0.05, c’est un ajustement de 5%.

Du côté sombre, elle domine complètement. Un gris très sombre peut avoir une luminance de 0.008. Ajouter 0.05 la porte à 0.058 : la constante est désormais sept fois plus grande que la mesure réelle. La formule a cessé de décrire les couleurs pour décrire la constante.

La conséquence pratique : de faibles écarts de luminance entre couleurs sombres sont gonflés en ratios qui paraissent élevés. Deux gris sombres réellement difficiles à distinguer peuvent obtenir une validation confortable.

Ce que cela donne en pratique

L’erreur typique du mode sombre consiste à reprendre une couleur de marque qui fonctionne sur blanc et à l’utiliser telle quelle sur un fond sombre.

Prenez un bleu moyen comme #2563eb. Sur blanc, il atteint environ 5.17:1, une validation AA nette pour le texte courant. Posez-le sur un quasi-noir #0c0f15 et WCAG 2 donne environ 3.6:1. C’est en dessous du niveau AA pour le texte courant : celui-là, vous le repéreriez.

Mais éclaircissez-le légèrement et vous pouvez atteindre 4.5:1 alors que le texte paraît toujours terne et vibre un peu sur le fond sombre. Le ratio dit que ça passe. Vos yeux disent le contraire. Et sur fond sombre, la pénalité perceptive d’un résultat limite est bien plus sévère que sur blanc.

C’est exactement ce qui nous est arrivé en construisant notre propre site. La couleur de nos liens en mode sombre passait confortablement le niveau WCAG AA, et quand nous l’avons soumise à l’APCA, elle est ressortie à Lc 37 pour une exigence de 75. Elle avait l’air correcte sur une capture d’écran et était réellement difficile à lire.

L’APCA gère correctement la polarité

L’APCA produit une valeur signée. Positive, elle désigne un texte sombre sur fond clair ; négative, un texte clair sur fond sombre. Elle utilise des courbes différentes pour chaque sens, car la réponse perceptive est réellement asymétrique : un texte clair sur fond sombre semble déborder vers l’extérieur et gagner en graisse apparente, un effet qui ne se produit pas dans l’autre sens.

C’est pourquoi l’APCA est le chiffre le plus fiable, précisément en mode sombre. Elle a été conçue en tenant compte de cette asymétrie, alors que WCAG 2 traite les deux sens avec la même arithmétique.

Que faire

Donnez deux variantes à chaque couleur de marque. L’une assombrie pour les fonds clairs, l’autre éclaircie pour les fonds sombres. Même teinte, même chroma, deux valeurs de luminosité. C’est le changement structurel le plus utile, et c’est ce qu’exige réellement une livraison dans les deux thèmes. Une seule couleur ne peut pas servir aux deux.

N’utilisez pas le noir pur comme fond sombre. #000000 maximise la distorsion due au reflet et produit un halo inconfortable avec du texte clair. Une valeur comprise entre #0c0f15 et #16181d est plus agréable à lire et vous laisse de la marge pour construire une échelle d’élévation.

N’utilisez pas non plus de texte blanc pur. Un blanc à pleine luminosité sur un quasi-noir est agressif et provoque un effet de bavure visible aux petites tailles. Un blanc cassé légèrement chaud, autour de #e8ecf2, se lit mieux et se mesure à peine différemment.

Réduisez la saturation à mesure que vous éclaircissez. Les couleurs très saturées vibrent sur fond sombre. Baisser la chroma à mesure que vous augmentez la luminosité donne un résultat plus calme, qui se mesure de la même façon.

Vérifiez le mode sombre à la taille et à la graisse réelles. L’écart entre les deux modèles se creuse quand le texte rapetisse : un libellé de 14px en mode sombre est donc le pire cas sur les deux axes à la fois.

Obtenir les bonnes variantes

L’approche efficace consiste à garder la teinte et la chroma et à ne déplacer que la luminosité, car c’est la luminosité qui détermine le contraste et la teinte qui porte votre marque. Travaillez en OKLCH, où la luminosité est perceptuellement uniforme : des paliers égaux paraissent égaux.

Vous obtenez ainsi un modèle à deux tokens :

--brand: oklch(55% 0.16 255);
--brand-on-light: oklch(48% 0.16 255);
--brand-on-dark:  oklch(78% 0.12 255);

Notez la baisse de chroma sur la variante sombre. C’est le point sur la désaturation évoqué plus haut, mis en pratique.

Notre vérificateur de contraste affiche WCAG 2 et l’APCA côte à côte et signale quand ils divergent, ce qui arrive souvent sur fond sombre. La piste de luminosité montre quelles portions de votre teinte passent sur un fond donné, pour que vous puissiez choisir les deux variantes sur le même axe.