Bhinish Dhiman
Intégrer la vérification du contraste à la CI pour faire échouer le build
Traduit de l’original en anglais. Lire l’original
Placez vos tokens de couleur dans un test qui confronte chaque paire texte sur surface à son exigence de contraste, et exécutez-le en CI. Un changement de token qui casse le contraste fait alors échouer le build comme n'importe quelle autre régression, au lieu de partir en production en attendant un audit.
Le contraste est vérifié quand quelqu’un pense à le vérifier. Tout le problème est là. C’est une étape manuelle en fin de processus, elle est en concurrence avec une échéance, et elle perd.
La solution est d’en faire une assertion automatisée, comme pour tous les autres types de régression.
Pourquoi la revue manuelle échoue
Elle arrive trop tard. Le contraste est généralement revu vers la fin, quand la couleur porte déjà tout un pan de l’interface et que la changer oblige à rouvrir des décisions tranchées.
Elle ne survit pas aux refactorisations. Six mois après l’audit, quelqu’un éclaircit un gris d’un cran dans le fichier de tokens, et rien ne le détecte.
Elle ne suit pas la multiplication des thèmes. Clair et sombre doublent la surface à vérifier, et c’est côté sombre que WCAG 2 est le moins fiable.
Personne ne revérifie un design system. Un seul token au contraste insuffisant se propage à tous les produits qui l’utilisent.
Chacun de ces points est la signature de quelque chose qui devrait être un test.
Ce qu’il faut vérifier
Pas toutes les paires possibles, mais celles que vous livrez réellement. Pour la plupart des systèmes, la liste est courte :
- Le texte courant sur le fond de page
- Le texte courant sur une carte ou une surface surélevée
- Le texte atténué ou secondaire sur les deux
- La couleur des liens sur les deux
- Le libellé de bouton sur le fond du bouton, pour chaque variante
- La couleur de bordure sur le fond qui l’entoure, si la bordure identifie un contrôle
Multipliez par le nombre de thèmes. Douze à vingt assertions couvrent la plupart des design systems.
Un exemple concret
import { describe, expect, it } from 'vitest';
import { contrastRatio } from './wcag';
const LIGHT = {
ink: '#10141c',
inkSoft: '#2a313d',
muted: '#5a6473',
paper: '#f7f8fa',
card: '#ffffff',
accent: '#006ad5',
};
const PAIRS = [
{ name: 'body on paper', fg: LIGHT.inkSoft, bg: LIGHT.paper, required: 4.5 },
{ name: 'body on card', fg: LIGHT.inkSoft, bg: LIGHT.card, required: 4.5 },
{ name: 'muted on paper', fg: LIGHT.muted, bg: LIGHT.paper, required: 4.5 },
{ name: 'heading on paper', fg: LIGHT.ink, bg: LIGHT.paper, required: 3 },
{ name: 'button label', fg: '#ffffff', bg: LIGHT.accent, required: 4.5 },
];
describe('design tokens pass contrast', () => {
for (const p of PAIRS) {
it(`${p.name} clears ${p.required}:1`, () => {
const ratio = contrastRatio(p.fg, p.bg);
expect(ratio, `${p.name}: got ${ratio.toFixed(2)}:1`).toBeGreaterThanOrEqual(p.required);
});
}
});
Le message d’échec compte. expected 4.21 to be greater than or equal to 4.5 lance quelqu’un dans une chasse au coupable. muted on paper: got 4.21:1 lui dit exactement quel token modifier.
Ajouter l’assertion APCA pour tout ce qui est petit
WCAG 2 ne voit pas la typographie : un ratio conforme sur un libellé de 12px ne prouve donc rien. Si l’expérience de lecture vous importe, et pas seulement le chiffre de conformité, vérifiez aussi l’APCA, en appliquant des seuils différents selon le rôle du texte.
L’APCA demande Lc 90 pour les colonnes de texte courant et Lc 60 pour le contenu et les intertitres. Appliquer le seuil du texte courant à un libellé de 13px le pousserait presque au noir et écraserait votre hiérarchie, ce qui n’aide personne. Vérifiez la norme qui s’applique réellement à chaque rôle.
Ce que nous avons découvert en le faisant
Nous l’avons ajouté à notre propre site après avoir construit l’outil, en nous attendant à ce que tout passe. Le test a trouvé sept échecs APCA dans l’interface du site, qui passaient pourtant WCAG 2 partout. Le pire était la couleur des liens en mode sombre, à Lc 37 pour une exigence de 75 : réellement difficile à lire, et invisible pour toute vérification fondée sur un ratio.
Deux des corrections étaient typographiques plutôt que chromatiques. Nos libellés en 11px ne pouvaient passer à aucun contraste : ils sont passés en 13px, graisse 500. Nos libellés de bouton ne pouvaient pas passer en blanc sur la couleur d’accent à 15px : ils sont passés en 17px, graisse 600.
Aucune de ces corrections n’est de celles qu’on trouve en ajustant une valeur hex.
Calculer la correction au lieu de deviner
Quand une assertion échoue, la tentation est d’assombrir la couleur jusqu’à ce qu’elle passe. C’est ainsi que les palettes deviennent ternes avec le temps.
Mieux vaut calculer le changement minimal : garder la teinte et la chroma, et chercher sur l’axe de luminosité la valeur la plus proche qui franchit le seuil. Vous obtenez le plus petit changement visuel qui règle le problème, plutôt qu’un changement arbitraire, et la couleur reste reconnaissable.
Nous avons généré ainsi chaque valeur corrigée de notre propre palette, au lieu de choisir à l’œil. Plusieurs changements étaient inférieurs à ΔE 1 : imperceptibles, et pourtant toute la différence entre l’échec et la réussite.
La dimension culturelle
Si cela fonctionne, la raison n’est pas technique. Un build en échec est un signal partagé et sans ambiguïté, alors qu’une revue d’accessibilité est l’avis d’une seule personne face à une date de livraison.
Une fois que le contraste est un test, personne n’a plus à plaider pour lui. C’est rouge, tout simplement, et le rouge se corrige.
Notre vérificateur de contraste en est le volet interactif : quand un test échoue, il vous donne la couleur conforme la plus proche, à recoller dans le fichier de tokens.