Le bouton invisible : la règle CSS qui casse sans rien signaler – AutomationBoost
◈ Cas réel · Édition 2026

Mon bouton d'achat était invisible

Cas de production·3 pages touchées ·Contraste mesuré 1,0 : 1·Niveau débutant

Le 20 août 2026, j'ai découvert que le bouton d'achat de ma page de vente était invisible. Pas cassé : invisible. Le libellé s'affichait en doré, sur un fond doré.

Quatre de mes automatisations envoyaient les gens vers cette page. Ils cliquaient, ils arrivaient, ils voyaient un rectangle jaune sans un mot dessus. Je ne sais toujours pas depuis combien de temps.

⚠ Le vrai sujet

Le code était juste. Le navigateur n'a rien signalé. Aucun outil ne plante quand un texte devient illisible — c'est la classe de bug la plus coûteuse : celle qui ne crie pas.

1 · La cause : deux règles, une seule gagne

Deux règles CSS visaient le même élément :

Le conflit
.content-block a { color: var(--gold); }   /* spécificité 0,1,1 */
.source-cta      { color: #000; }           /* spécificité 0,1,0 */

Instinctivement on lit la seconde comme « plus précise » : elle cible une classe unique, faite pour ce bouton. C'est le contraire. Le navigateur ne juge pas l'intention, il compte.

Comment le compte se fait

Trois nombres, lus de gauche à droite comme un score. Le premier qui diffère tranche, quel que soit l'ordre dans le fichier.

PositionCe qu'on compteExemple
1 — identifiantschaque #id#panier → 1,0,0
2 — classesclasses, attributs, pseudo-classes.source-cta → 0,1,0
3 — élémentsbalises et pseudo-élémentsa, ::after → 0,0,1

.content-block a vaut 0,1,1 — une classe et une balise. .source-cta vaut 0,1,0. Égalité sur les classes, puis 1 contre 0 sur les éléments. La première gagne, et le bouton hérite de la couleur des liens.

2 · Le correctif, et celui qu'il ne faut pas prendre

Le réflexe est !important. Il marche, et il rend la feuille de style impossible à débugger la fois suivante : la prochaine exception devra surenchérir.

Requalifier la classe par son contexte suffit :

Le correctif
.content-block a.source-cta { color: #000; }   /* 0,2,1 — deux classes, une balise */

Deux classes contre une : la règle reprend la main sans jamais toucher à la cascade générale.

3 · Le contrôle qui l'attrape

Vingt lignes à coller dans la console de n'importe quelle page. Il compare la couleur calculée de chaque texte visible à celle du fond derrière lui, et liste tout ce qui passe sous le seuil lisible — 4,5 : 1 pour du texte courant, 3 : 1 pour du gros.

Contrôle de contraste — console
// Colle ceci dans la console de ta page. Il compare, pour chaque element
// visible portant du texte, la couleur calculee du texte et celle du fond
// derriere lui, et signale tout ce qui passe sous le seuil lisible.
(function () {
  const lum = (c) => {
    const [r, g, b] = c.match(/\d+/g).map(Number).map((v) => {
      v /= 255;
      return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
    });
    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
  };
  const fond = (el) => {
    let n = el;
    while (n && n !== document.documentElement) {
      const bg = getComputedStyle(n).backgroundColor;
      if (bg && !/rgba\(0, 0, 0, 0\)|transparent/.test(bg)) return bg;
      n = n.parentElement;
    }
    return 'rgb(255, 255, 255)';
  };
  const vus = [];
  document.querySelectorAll('*').forEach((el) => {
    if (!el.offsetParent && getComputedStyle(el).position !== 'fixed') return;
    const txt = [...el.childNodes]
      .filter((n) => n.nodeType === 3 && n.textContent.trim())
      .map((n) => n.textContent.trim()).join(' ');
    if (!txt) return;
    const s = getComputedStyle(el);
    const l1 = lum(s.color), l2 = lum(fond(el));
    const ratio = (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
    const gros = parseFloat(s.fontSize) >= 24 || (parseFloat(s.fontSize) >= 18.66 && +s.fontWeight >= 700);
    const seuil = gros ? 3 : 4.5;
    if (ratio < seuil) vus.push({ ratio: +ratio.toFixed(2), seuil, texte: txt.slice(0, 40), el });
  });
  if (!vus.length) { console.log('Aucun texte sous le seuil.'); return; }
  console.table(vus.sort((a, b) => a.ratio - b.ratio));
})();
⚡ Le détail qui compte

Il remonte l'arbre pour trouver le vrai fond. Un texte posé sur un conteneur transparent hérite du fond du parent — c'est exactement ce qui rendait mon bouton illisible sans qu'aucune règle ne paraisse fautive.

4 · Ce que ça a changé chez moi

J'ai lancé le contrôle sur toutes les pages du site. Trois étaient touchées, pas une. Le même bouton, la même cause, dupliquée par copier-coller.

Depuis, je ne me fie plus au rendu supposé : je capture la page servie et je la regarde. Un rendu techniquement parfait peut masquer exactement ce qu'on voulait montrer.

Ce qui est cassé ne crie pas. C'est la seule leçon de cette histoire, et elle vaut bien au-delà du CSS.

Si tu veux qu'on regarde ton site

Ce contrôle attrape les contrastes. Il ne dit pas si ta page convertit. Si tu veux qu'on regarde ce que ton site coûte et où il perd des gens, on commence par un audit — et si le calcul ne tombe pas en ta faveur, je te le dis.

Recevoir mon audit

LE BOUTON INVISIBLE | TONY PAYET / AUTOMATION BOOST | 2026 EDITION

← Toutes les ressources