EXPERT 03 — Socle couleur, accessibilité WCAG 2.2 et performance — TERO GROUP
Périmètre : tero.be (maison mère) et toutes les sous-activités du groupe Tero.
Objet : socle couleur + accessibilité + performance, chiffré, vérifiable, directement
intégrable dans tero-group-rules.md, le design system et les skills.
Méthode : recalcul intégral des contrastes (formule WCAG de luminance relative),
relevé réseau du 15 octobre 2026 sur https://tero.be/fr/, lecture critique de
tero-group-rules.md v1.0.0 (645 lignes), DESIGN.md, tokens.css, design-tokens.json
et du modèle _ref/teropadelclub/…/tero-rules.md.
Étiquettes : [MESURE] = relevé ou calcul reproductible · [RÈGLE] = décision de système
proposée, opposable une fois validée · [À VALIDER CLIENT] = question ouverte.
Aucun fait de marque n'est inventé ici : les couleurs utilisées sont celles mesurées dans
l'audit du 15/10/2026 ; les seules valeurs nouvelles proposées sont des neutres de système
(texte et bordure), explicitement étiquetées.
---
1. Verdict sur l'existant — ce qui tient, ce qui casse
1.1 Ce qui est exact (vérifié par recalcul indépendant)
Les 21 ratios de contraste publiés dans le contexte, dans docs/AUDIT-TERO-BE-2026.md § 3.5,
dans tero-group-rules.md § 2.4 et dans design-tokens.json ont été recalculés un par un avec la
formule WCAG (sRGB → linéarisation → 0,2126 R + 0,7152 V + 0,0722 B → (L1+0,05)/(L2+0,05)).
Résultat : 21/21 exacts, écart maximal 0,00. La base chiffrée du lead est fiable, y compris
pour les dérives (#5E93A7 = 3,38 · #B00000 = 7,38 · #680224 = 12,94 · #323232 = 12,82). [MESURE]
Cela signifie que les erreurs relevées ci-dessous ne sont pas des erreurs de calcul : ce sont
des erreurs de règle (une valeur juste utilisée dans un contexte où elle échoue).
1.2 Les 20 constats qui cassent le système — par priorité
Priorité P0 = bloque la conformité, à trancher avant toute production de composants.
Priorité P1 = à corriger avant la première livraison. P2 = dette de qualité.
| # | Prio | Constat | Où (fichier:ligne) | Preuve chiffrée | Correction attendue |
|---|---|---|---|---|---|
| F1 | P0 | L'anneau de focus teal est unique et imposé partout : il disparaît sur les surfaces que la charte autorise (bouton teal, bouton pétrole, aplat rose vif, aplat cyan, bande #212121). | tero-group-rules.md:372 et 438 · tokens.css (--focus-ring) · design-tokens.json (components.focus) | teal/teal = 1,00:1 · teal/#004B69 = 1,75 · teal/#FF1D58 = 1,44 · teal/#00B7FF = 2,38 · teal/#212121 = 2,97 (sous le seuil non-texte de 3:1) | Double anneau à jeton contextuel (§ 3.3). Un seul anneau ne peut pas être conforme sur tout le système. |
| F2 | P0 | #B9B9B9 (--tero-muted) est affecté aux « libellés de champ » et aux placeholders. Le placeholder d'un champ actif n'est pas exempté par WCAG 1.4.3 : il doit atteindre 4,5:1. | tero-group-rules.md:129 et 436 · DESIGN.md § 2 · tokens.css | #B9B9B9/#FFFFFF = 1,96:1 (échec AA, facteur 2,3) | Nouveau neutre --tero-ink-muted: #5F5F5F (6,39 / 5,83 / 5,10 / 4,52 sur les 4 fonds clairs). #B9B9B9 devient réservé au désactivé (seul cas exempté). |
| F3 | P0 | La bordure de champ #D9D9D9 sur fond blanc, et le champ #E5E6E5 bordé de #D9D9D9 : la limite du contrôle est invisible. Aucun jeton de bordure interactive n'existe. | tero-group-rules.md:378 et 411 · tokens.css (--tero-line, --tero-line-soft) | #D9D9D9/#FFFFFF = 1,41:1 · #D9D9D9/#E5E6E5 = 1,13:1 · seuil 1.4.11 = 3:1 | Ajouter --tero-border-field: #767676 (4,54 / 4,15 / 3,63 / 3,22). #D9D9D9 reste décoratif (filets, séparateurs). |
| F4 | P0 | Contradiction à trois voix sur le teal en bande profonde : tero-group-rules.md § 7.3 le liste comme « accent autorisé » puis l'interdit en texte ; DESIGN.md § 6 l'exclut ; design-tokens.json ne le liste pas dans deepBand.accents. Sur #212121 le teal tombe même sous le seuil graphique de 3:1. | tero-group-rules.md:385-386 · DESIGN.md § 6 · design-tokens.json (components.deepBand) | teal/#000000 = 3,87 · teal/#212121 = 2,97 | Règle unique : teal interdit sur #212121 (texte ET graphique) ; sur #000000 toléré uniquement comme graphique décoratif ≥ 3:1, jamais comme texte. |
| F5 | P0 | Les largeurs de contrôle et de capture s'arrêtent à 360 px. WCAG 2.2 SC 1.4.10 Reflow exige l'absence de défilement horizontal à 320 px (équivalent zoom 400 %). | tero-group-rules.md:407 et 558 · DESIGN.md § 4 | 320 px absent des deux listes | Ajouter 320 px en tête de la liste des 5 largeurs → 6 largeurs : 320 / 360 / 390 / 768 / 1024 / 1440. |
| F6 | P0 | Aucun jeton d'état n'existe (succès, avertissement, erreur, information) alors que § 7.4 exige que tout composant déclare l'état « erreur ». Les 7 couleurs déclarées ne portent aucune règle d'état. | tokens.css · design-tokens.json (aucune entrée) · tero-group-rules.md:388-391 | 0 jeton d'état pour 7 états exigés | § 3.4 : table d'états opposable, dérivée sans nouvelle couleur de marque. |
| F7 | P1 | Le mot-symbole est décrit en capitales (« le mot-symbole s'écrit TERO »). Le logo officiel est en minuscules : _audit/logo-tero-2025.svg, viewBox 0 0 100 40, 5 tracés, fill noir. | tero-group-rules.md:257, 276, 292 · DESIGN.md § 8 · design-tokens.json (brand.wordmarkCase) | Fichier de référence : minuscules | Corriger partout : mot-symbole = tero (minuscules) ; graphie rédactionnelle = Tero ; jamais TERO en capitales. |
| F8 | P1 | La description du monogramme est fausse : le glyphe officiel est le o stylisé (anneau plein + barre horizontale sous l'anneau), pas « un anneau ». Décrit comme « anneau » dans les règles et le JSON. | tero-group-rules.md:262 · design-tokens.json (brand.logoForbidden) | _audit/monogram-tero-2026.png, 1272 × 1861 | Reformuler : « glyphe o isolé de tero ». L'usage reste interdit comme Organization.logo (ratio 0,68, portrait) mais devient possible comme pastille/monogramme avec alt = tero. |
| F9 | P1 | Le seuil « grand texte » est mal énoncé : « ≥ 3:1 pour ≥ 24 px gras ». WCAG : 3:1 pour ≥ 24 px normal OU ≥ 18,66 px (14 pt) gras. Énoncé actuel = faux négatif sur les gros boutons. | tero-group-rules.md:600 | 14 pt gras = 18,66 px | « ≥ 3:1 pour ≥ 24 px, ou ≥ 18,66 px en graisse 700 ». |
| F10 | P1 | Le socle exige font-display: swap + préchargement des polices. Le site ne le fait pas : 4 @font-face en font-display: auto, 0 preload de police. | tero-group-rules.md:450-452 vs relevé live | 4 @font-face, toutes font-display: auto · 0 link rel=preload as=font | swap obligatoire + 1 seul preload (graisse du H1) + repli métrique (size-adjust). |
| F11 | P1 | § 11 Performance = 3 lignes, aucun budget chiffré hors LCP/INP/CLS. Impossible d'opposer un refus à une page de 400 Ko de CSS. | tero-group-rules.md:449-453 | CSS mesuré : 170,5 Ko (gzip) pour 10 des ≥ 34 feuilles de l'accueil | § 5.2 : budget complet, mesurable, avec l'outil de contrôle. |
| F12 | P1 | Les 9 critères nouveaux de WCAG 2.2 sont absents (§ 10 en compte 0), alors que le niveau visé est « AA minimum partout ». | tero-group-rules.md:433-442 | 9 SC absents : 2.4.11, 2.4.12, 2.4.13, 2.5.7, 2.5.8, 3.2.6, 3.3.7, 3.3.8, 3.3.9 | § 4.1 et § 4.2. |
| F13 | P1 | Aucune règle contraste élevé / forced-colors / prefers-contrast, aucun test de zoom 200 %, aucun test 320 px, aucune règle de sous-titres vidéo. | tero-group-rules.md:433-442 · § 16.2 (8 contrôles, 0 a11y automatisé) | 0 occurrence de forced-colors / prefers-contrast / 1.2.2 dans les 645 lignes | § 4.2 et § 4.3. |
| F14 | P1 | Le relevé live montre 2 H1 et 0 H2/H3 sur l'accueil (100 premiers ko de HTML) : la hiérarchie de titres est cassée, alors que § 6.1 l'interdit. | tero-group-rules.md:328 vs relevé live 15/10/2026 | h1 = 2 · h2 = 0 · h3 = 0 | Contrôle automatisé : exactement 1 H1, aucun saut de niveau (§ 4.3, porte CI). |
| F15 | P2 | La dérive sauge est exposée comme jeton CSS utilisable (--tero-drift-sage). Un jeton exposé finit utilisé. | tokens.css | --tero-drift-sage: #97ab83 | Préfixer les dérives --x-drift- hors du bloc :root livré, ou les sortir de tokens.css. |
| F16 | P2 | L'audit classe #323232 en « dérive » (§ 3.5) alors que la charte l'adopte comme neutre n° 2 (--tero-ink-soft). Incohérence de qualification entre les deux documents. | AUDIT:193 vs tero-group-rules.md:127 | même valeur, deux statuts | Dans l'audit : « neutre adopté, non déclaré dans le kit Elementor ». |
| F17 | P2 | design-tokens.json annonce « method: WCAG 2.1 » alors que la cible est 2.2 (formules identiques, libellé faux). | design-tokens.json (contrast.method) | — | « WCAG 2.2 AA, formule de luminance relative W3C ». |
| F18 | P2 | La liste des dérives du JSON omet le magenta #FF2E82 et le lime #E9F44E mesurés sur teropadelclub.be, pourtant cités par l'audit § 8. | design-tokens.json (color.drift) | magenta #FF2E82 / blanc = 3,20:1 · lime #E9F44E / blanc = 1,20:1 | Ajouter les deux valeurs, avec mention « palette padel, sous-marque — à trancher (question client n° 2) ». |
| F19 | P2 | DESIGN.md § 9 liste 12 anti-patterns, aucun d'accessibilité (focus supprimé, placeholder comme label, état en couleur seule, texte sur fond non conforme). | DESIGN.md § 9 | 0/12 | Ajouter les 4 anti-patterns a11y (§ 4.4). |
| F20 | P2 | § 16.2 « Contrôles minimaux avant livraison » = 8 contrôles, aucun automatisé pour l'accessibilité et la performance (pas d'axe, pas de Lighthouse, pas de budget de poids). | tero-group-rules.md:552-561 | 0/8 | Ajouter les 5 portes CI du § 4.3. |
---
2. Matrice de contraste complète (recalculée)
2.1 Méthode exacte — reproductible
Formule W3C utilisée pour tous les chiffres de ce document (aucune valeur recopiée) :
// sRGB -> luminance relative
c = canal / 255
c_lin = (c <= 0.03928) ? c / 12.92 : ((c + 0.055) / 1.055) ^ 2.4
L = 0.2126R_lin + 0.7152V_lin + 0.0722*B_lin
ratio = (max(L1,L2) + 0.05) / (min(L1,L2) + 0.05)
Seuils appliqués : texte 4,5:1 (AA) / 7:1 (AAA) ; grand texte 3:1 (≥ 24 px, ou
≥ 18,66 px en graisse 700) ; non-texte (bordure, icône porteuse de sens, anneau de focus,
graphique) 3:1 (SC 1.4.11).
Luminances relatives calculées (utiles aux calculs de dérivées) :
| Couleur | Hex | L |
|---|---|---|
| Blanc | #FFFFFF | 1,000000 |
| Surface | #F6F4F4 | 0,908259 |
| Filet léger | #E5E6E5 | 0,789088 |
| Filet | #D9D9D9 | 0,693872 |
| Sable | #FAD282 | 0,680289 |
| Rose poudré | #D7B4B4 | 0,503849 |
| Gris désactivé | #B9B9B9 | 0,485150 |
| Cyan | #00B7FF | 0,410870 |
| Rose vif | #FF1D58 | 0,228433 |
| Teal | #007864 | 0,143530 |
| Terracotta | #B93815 | 0,131968 |
| Pétrole | #004B69 | 0,060521 |
| Encre douce | #323232 | 0,031896 |
| Encre profonde | #212121 | 0,015209 |
| Noir | #000000 | 0,000000 |
2.2 Neutres et couleurs de marque × fonds clairs — texte
Lecture : ratio sur fond #FFFFFF / #F6F4F4 / #E5E6E5 / #D9D9D9, puis verdict pour du texte courant.
| Texte | Hex | /#FFFFFF | /#F6F4F4 | /#E5E6E5 | /#D9D9D9 | Rôle texte autorisé |
|---|---|---|---|---|---|---|
| Noir | #000000 | 21,00 AAA | 19,17 AAA | 16,78 AAA | 14,88 AAA | Texte principal sur les 4 fonds |
| Encre profonde | #212121 | 16,10 AAA | 14,70 AAA | 12,87 AAA | 11,41 AAA | Titres, aplats sombres — texte sur les 4 fonds |
| Encre douce | #323232 | 12,82 AAA | 11,70 AAA | 10,25 AAA | 9,08 AAA | Texte secondaire sur les 4 fonds |
| Pétrole | #004B69 | 9,50 AAA | 8,67 AAA | 7,59 AAA | 6,73 AA | Texte, titres, liens sur les 4 fonds |
| Teal | #007864 | 5,43 AA | 4,95 AA | 4,34 ÉCHEC | 3,84 ÉCHEC | Texte uniquement sur #FFFFFF et #F6F4F4 |
| Terracotta | #B93815 | 5,77 AA | 5,27 AA | 4,61 AA | 4,09 ÉCHEC | Texte sur blanc, surface, filet léger — jamais sur #D9D9D9 |
| Rose vif | #FF1D58 | 3,77 grand | 3,44 grand | 3,01 grand | 2,67 ÉCHEC | Grand texte (≥ 24 px / ≥ 18,66 px gras) sur fonds très clairs uniquement |
| Gris désactivé | #B9B9B9 | 1,96 ÉCHEC | 1,79 ÉCHEC | 1,57 ÉCHEC | 1,39 ÉCHEC | Aucun texte actif. Désactivé uniquement (exempt SC 1.4.3) |
| Sable | #FAD282 | 1,44 ÉCHEC | 1,31 ÉCHEC | 1,15 ÉCHEC | 1,02 ÉCHEC | Aucun texte — aplat seulement |
| Rose poudré | #D7B4B4 | 1,90 ÉCHEC | 1,73 ÉCHEC | 1,52 ÉCHEC | 1,34 ÉCHEC | Aucun texte — aplat seulement |
| Cyan | #00B7FF | 2,28 ÉCHEC | 2,08 ÉCHEC | 1,82 ÉCHEC | 1,61 ÉCHEC | Aucun texte — aplat seulement |
| Filet | #D9D9D9 | 1,41 ÉCHEC | 1,29 ÉCHEC | 1,13 ÉCHEC | 1,00 ÉCHEC | Aucun texte — séparateur décoratif |
| Surface | #F6F4F4 | 1,10 ÉCHEC | 1,00 ÉCHEC | 1,14 ÉCHEC | 1,29 ÉCHEC | Aucun texte — fond |
| Blanc | #FFFFFF | 1,00 ÉCHEC | 1,10 ÉCHEC | 1,25 ÉCHEC | 1,41 ÉCHEC | Aucun texte — fond |
Trois échecs non documentés par le lead, à intégrer :
- teal sur #E5E6E5 = 4,34:1 et teal sur #D9D9D9 = 3,84:1 → un lien teal posé sur un fond de
champ ou sur une plaque filet échoue AA. [MESURE]
- terracotta sur #D9D9D9 = 4,09:1 → un chiffre terracotta sur plaque échoue AA. [MESURE]
- rose vif = 3,01:1 sur #E5E6E5 → le seuil « grand texte » n'est plus tenu dès que le fond est
gris clair (3,01 est à la limite, marge nulle). [MESURE]
2.3 Fonds sombres — texte et graphique
| Texte | Hex | /#000000 | /#212121 | Rôle autorisé |
|---|---|---|---|---|
| Blanc | #FFFFFF | 21,00 AAA | 16,10 AAA | Texte principal sur bande profonde |
| Sable | #FAD282 | 14,61 AAA | 11,20 AAA | Texte, sur-titre, accent, graphique |
| Filet | #D9D9D9 | 14,88 AAA | 11,41 AAA | Filet visible sur fond sombre |
| Rose poudré | #D7B4B4 | 11,08 AAA | 8,49 AAA | Texte, accent |
| Gris désactivé | #B9B9B9 | 10,70 AAA | 8,21 AAA | Texte secondaire sur bande profonde (et désactivé) |
| Cyan | #00B7FF | 9,22 AAA | 7,07 AAA | Texte, accent, graphique |
| Rose vif | #FF1D58 | 5,57 AA | 4,27 AA | Accent rare, texte ≥ 18,66 px gras |
| Teal | #007864 | 3,87 grand/UI | 2,97 ÉCHEC | Sur #000000 : graphique décoratif uniquement. Sur #212121 : interdit totalement |
| Terracotta | #B93815 | 3,64 grand/UI | 2,79 ÉCHEC | Sur #000000 : graphique uniquement. Sur #212121 : interdit (utiliser un badge terracotta + texte blanc) |
| Pétrole | #004B69 | 2,21 ÉCHEC | 1,69 ÉCHEC | Interdit sur toute bande profonde, texte comme graphique |
| Encre douce | #323232 | 1,64 ÉCHEC | 1,26 ÉCHEC | Interdit sur bande profonde |
| Encre profonde | #212121 | 1,30 ÉCHEC | 1,00 ÉCHEC | Interdit |
| Noir | #000000 | 1,00 ÉCHEC | 1,30 ÉCHEC | Interdit |
Conséquence opposable : sur bande profonde, seuls **blanc, sable, rose poudré, cyan, rose vif
(≥ 18,66 px gras) et gris désactivé #B9B9B9 (texte secondaire)** sont utilisables en texte.
Le teal et le terracotta n'y sont que des graphiques sur noir pur, et jamais sur #212121.
2.4 Texte sur aplat de marque — la règle du texte noir
| Aplat | Hex | texte noir #000 | texte blanc #FFF | Verdict opposable |
|---|---|---|---|---|
| Teal | #007864 | 3,87 | 5,43 AA | Texte blanc |
| Pétrole | #004B69 | 2,21 | 9,50 AAA | Texte blanc |
| Terracotta | #B93815 | 3,64 | 5,77 AA | Texte blanc |
| Sable | #FAD282 | 14,61 AAA | 1,44 | Texte noir |
| Rose poudré | #D7B4B4 | 11,08 AAA | 1,90 | Texte noir |
| Cyan | #00B7FF | 9,22 AAA | 2,28 | Texte noir |
| Rose vif | #FF1D58 | 5,57 AA | 3,77 grand | Texte noir (jamais blanc sous 18,66 px gras) |
| Gris désactivé | #B9B9B9 | 10,70 AAA | 1,96 | Texte noir — aplat désactivé |
| Filet | #D9D9D9 | 14,88 AAA | 1,41 | Texte noir |
Règle des trois familles [RÈGLE] :
- Famille A — aplats à texte blanc : teal, pétrole, terracotta. Interdits en aplat sous du texte noir.
- Famille B — aplats à texte noir : sable, rose poudré, cyan, rose vif, #B9B9B9, #D9D9D9.
Interdits en texte sur fond clair.
- Famille C — neutres de texte : #000000, #212121, #323232, #004B69 (+ #5F5F5F proposé).
Jamais utilisés comme aplat sous du texte blanc sauf #004B69 (9,50).
2.5 Accents sur aplats clairs (texte posé sur une pastille sable / rose / cyan)
| Texte | sur sable #FAD282 | sur rose #D7B4B4 | sur cyan #00B7FF | Verdict |
|---|---|---|---|---|
| #000000 | 14,61 | 11,08 | 9,22 | ✅ AAA sur les trois |
| #323232 | 8,92 | 6,76 | 5,63 | ✅ AA/AAA sur les trois |
| #004B69 | 6,61 | 5,01 | 4,17 | ✅ sur sable et rose — échoue sur cyan (4,17) |
| #B93815 | 4,01 | 3,04 | 2,53 | ⚠️ grand texte sur sable seulement — sinon interdit |
| #007864 | 3,77 | 2,86 | 2,38 | ❌ interdit en texte sur aplat |
| #FF1D58 | 2,62 | 1,99 | 1,66 | ❌ interdit |
Règle : sur un aplat coloré, le texte est noir, sauf sur sable et rose poudré où le pétrole
#004B69 est également autorisé (6,61 / 5,01). Jamais de teal, jamais de rose vif en texte sur aplat.
2.6 Anneau de focus — pourquoi un seul anneau ne peut pas marcher
Ratio de l'anneau (couleur) contre le fond adjacent à l'anneau. Seuil 3:1 (SC 1.4.11 + 2.4.13).
| Anneau \ Fond | #FFFFFF | #F6F4F4 | #000000 | #212121 | teal | pétrole | rose vif | cyan |
|---|---|---|---|---|---|---|---|---|
| teal (actuel) | 5,43 ✅ | 4,95 ✅ | 3,87 ✅ | 2,97 ❌ | 1,00 ❌ | 1,75 ❌ | 1,44 ❌ | 2,38 ❌ |
| noir | 21,00 ✅ | 19,17 ✅ | 1,00 ❌ | 1,30 ❌ | 3,87 ✅ | 2,21 ❌ | 5,57 ✅ | 9,22 ✅ |
| blanc | 1,00 ❌ | 1,10 ❌ | 21,00 ✅ | 16,10 ✅ | 5,43 ✅ | 9,50 ✅ | 3,77 ✅ | 2,28 ❌ |
| sable | 1,44 ❌ | 1,31 ❌ | 14,61 ✅ | 11,20 ✅ | 3,77 ✅ | 6,61 ✅ | 2,62 ❌ | 1,58 ❌ |
| rose vif | 3,77 ✅ | 3,44 ✅ | 5,57 ✅ | 4,27 ✅ | 1,44 ❌ | 2,52 ❌ | 1,00 ❌ | 1,66 ❌ |
Aucune ligne n'est conforme sur toute la largeur. Le teal — imposé aujourd'hui — est conforme
sur 4 fonds sur 8 et échoue sur 4, dont deux surfaces directement produites par la charte
(bouton teal § 7.1, bande #212121 § 7.3). D'où la solution du double anneau (§ 3.3), seule
construction qui reste ≥ 3:1 quel que soit le fond.
2.7 Non-texte (SC 1.4.11) — bordures, icônes, graphiques porteurs de sens
| Élément | Ratio mesuré | Seuil 3:1 | Décision |
|---|---|---|---|
| Bordure de champ #D9D9D9 sur blanc | 1,41 | ❌ | Remplacer par #767676 (4,54) |
| Bordure de champ #D9D9D9 sur #E5E6E5 | 1,13 | ❌ | Remplacer par #767676 (3,63) |
| Filet séparateur #D9D9D9 sur blanc | 1,41 | exempt | Décoratif uniquement — ne doit jamais être le seul indice d'un contrôle |
| Sous-titre / icône teal sur blanc | 5,43 | ✅ | OK |
| Icône cyan sur blanc | 2,28 | ❌ | Icône cyan uniquement dans un aplat, ou doubler l'information par un texte |
| Icône/soulignement rose vif sur blanc | 3,77 | ✅ | OK pour le non-texte |
| Bordure de carte sur bande noire (si filet #D9D9D9) | 14,88 | ✅ | OK |
| Bordure interactive sur bande noire | #767676 / #000000 = 4,62 | ✅ | Utiliser #767676 (ou #FFFFFF) |
| Indicateur de sélection (onglet actif, case cochée) | doit être ≥ 3:1 contre son voisin | — | À instrumenter : aucun jeton n'existe (voir § 3.1, --tero-border-field) |
2.8 Rôles autorisés par couleur — table de décision opposable
| Couleur | Hex | Texte | Aplat | Graphique / bordure | Focus | Interdit | Substitution si besoin |
|---|---|---|---|---|---|---|---|
| Noir | #000000 | ✅ principal (fonds clairs) | ✅ bande profonde | ✅ | ✅ anneau (fonds clairs) | texte sur sombre | — |
| Blanc | #FFFFFF | ✅ sur bande profonde et aplats teal/pétrole/terracotta | ✅ fond | ✅ | ✅ anneau (bandes profondes) | texte sur aplat sable/rose/cyan/vif | noir |
| Surface | #F6F4F4 | ❌ | ✅ fond de section | ✅ fond | — | texte | — |
| Filet | #D9D9D9 | ❌ | ❌ | ✅ séparateur décoratif | — | bordures de contrôles | #767676 |
| Filet léger | #E5E6E5 | ❌ | ✅ fond de champ | ❌ | — | bordures de contrôles | #767676 |
| Encre profonde | #212121 | ✅ titres | ✅ bande profonde | ✅ | — | texte sur sombre | — |
| Encre douce | #323232 | ✅ texte secondaire | ❌ | ✅ | — | texte sur sombre | — |
| Gris désactivé | #B9B9B9 | ✅ désactivé uniquement + texte secondaire sur fond sombre | ✅ aplat désactivé | ✅ sur fond sombre | — | placeholders, libellés, mentions | #5F5F5F |
| Teal | #007864 | ✅ texte/lien sur #FFFFFF et #F6F4F4 uniquement | ✅ texte blanc | ✅ sur fonds clairs | ⚠️ insuffisant seul (voir § 3.3) | texte sur #E5E6E5, #D9D9D9 ; texte et graphique sur #212121 ; texte sur noir | pétrole (9,50) |
| Pétrole | #004B69 | ✅ texte sur les 4 fonds clairs | ✅ texte blanc | ✅ sur fonds clairs | ✅ anneau sur fonds clairs (9,50) | toute bande profonde (2,21 / 1,69) | blanc/sable |
| Terracotta | #B93815 | ✅ texte sur blanc/surface/filet léger | ✅ texte blanc | ✅ | ⚠️ 2,79 sur #212121 | texte sur #D9D9D9, sur bandes profondes | badge terracotta + texte blanc |
| Sable | #FAD282 | ❌ | ✅ texte noir | ✅ sur fond sombre | ✅ anneau sur fonds sombres (14,61) | texte sur fond clair | #000000 |
| Rose poudré | #D7B4B4 | ❌ | ✅ texte noir | ✅ sur fond sombre | — | texte sur fond clair | #000000 |
| Cyan | #00B7FF | ❌ | ✅ texte noir | ❌ icône sur fond clair | — | texte et graphique sur fond clair | noir |
| Rose vif | #FF1D58 | ⚠️ grand texte seul (≥ 24 px / ≥ 18,66 px gras) | ✅ texte noir | ✅ | ✅ anneau sur fonds clairs (3,77) | texte blanc sur son aplat ; texte courant sur fond clair | #B93815 (texte) |
2.9 Échecs mesurés et substitution imposée
| Couple en échec | Ratio | Substitution |
|---|---|---|
| teal sur #E5E6E5 / #D9D9D9 | 4,34 / 3,84 | pétrole #004B69 (7,59 / 6,73) ou noir |
| terracotta sur #D9D9D9 | 4,09 | #000000 (14,88) ou #323232 (9,08) |
| #B9B9B9 texte actif | 1,96 | #5F5F5F (6,39) — proposé |
| #D9D9D9 bordure de champ | 1,41 | #767676 (4,54) — proposé |
| blanc sur rose vif sous 18,66 px gras | 3,77 | texte noir sur rose vif (5,57) |
| teal en texte sur noir | 3,87 | sable (14,61) ou blanc (21,00) |
| teal sur #212121 | 2,97 | sable (11,20) ou blanc (16,10) |
| terracotta sur bande profonde | 3,64 / 2,79 | badge terracotta + texte blanc (5,77) |
| pétrole sur bande profonde | 2,21 / 1,69 | aucun usage — interdiction totale |
| rose vif sur #E5E6E5 en grand texte | 3,01 | bord de marge nulle → noir |
| #FF4A4A (dérive) en erreur sur blanc | 3,32 | terracotta #B93815 (5,77) |
| magenta padel #FF2E82 sur blanc | 3,20 | terracotta sur le périmètre groupe ; hors groupe = dérive |
| lime padel #E9F44E sur blanc | 1,20 | aplat + texte noir uniquement |
---
3. Rôles sémantiques couleur (jetons) et états
3.1 Jetons sémantiques — à ajouter au design system
Les jetons de base existent (tokens.css § 2.1/2.2). Il manque la couche sémantique complète
et deux neutres de système. Proposition [RÈGLE] :
| Jeton sémantique | Valeur | Source | Usage | Preuve | Interdit |
|---|---|---|---|---|---|
| --surface | #FFFFFF | mesuré | Fond principal | — | — |
| --surface-alt | #F6F4F4 | mesuré | Fond de section alternée | — | texte teal/terracotta |
| --surface-elevated | #FFFFFF + filet 1 px --line | règle | Surface élevée = filet, jamais ombre (§ 7.2) | filet décoratif | fond + bordure + rayon + ombre empilés |
| --surface-inverse | #000000 | mesuré | Bande profonde | — | — |
| --surface-inverse-alt | #212121 | mesuré | Bande profonde alternative | — | teal, terracotta, pétrole |
| --text | #000000 | mesuré | Texte principal | 21,00 | sur fond sombre |
| --text-secondary | #323232 | mesuré | Texte secondaire, chapô, meta | 12,82 | sur fond sombre |
| --text-muted | #5F5F5F (proposé) | neutre de système | Placeholders, libellés, mentions, dates | 6,39 / 5,83 / 5,10 / 4,52 | — |
| --text-disabled | #B9B9B9 | mesuré | Composant inactif exclusivement | 1,96 (exempt SC 1.4.3) | jamais un placeholder actif |
| --text-inverse | #FFFFFF | mesuré | Texte sur bande profonde et aplats famille A | 21,00 | aplat famille B |
| --text-inverse-secondary | #B9B9B9 | mesuré | Texte secondaire sur bande profonde | 10,70 / 8,21 | fonds clairs |
| --border | #D9D9D9 | mesuré | Filet séparateur décoratif | 1,41 (exempt) | bordures de contrôles |
| --border-field | #767676 (proposé) | neutre de système | Bordure de champ, case, onglet, sélection | 4,54 / 4,15 / 3,63 / 3,22 | — |
| --border-inverse | #767676 | proposé | Bordure sur bande profonde | 4,62 / 3,55 | — |
| --accent | #007864 | mesuré | Accent n° 1 : lien en ligne, pastille, aplat | 5,43 (blanc) | #E5E6E5, #D9D9D9, bandes profondes |
| --accent-hover | #004B69 | mesuré | Survol/enfoncé de l'accent (pas de nouvelle couleur) | 9,50 | bandes profondes |
| --accent-on-dark | #FAD282 | mesuré | Accent sur bande profonde | 14,61 | fonds clairs |
| --focus-ring | #000000 (fonds clairs) / #FFFFFF (bandes profondes) | règle | Anneau de focus extérieur | 21,00 | — |
| --focus-halo | #FFFFFF / #000000 | règle | Anneau intérieur (gorge de séparation) | 21,00 | — |
| --success | #007864 | mesuré | Fond de badge succès, texte #FFFFFF | 5,43 | en couleur de texte d'état |
| --warning | #FAD282 | mesuré | Fond de badge avertissement, texte #000000 | 14,61 | en couleur de texte |
| --error | #B93815 | mesuré | Fond de badge erreur, texte #FFFFFF ; texte d'erreur sur fond clair | 5,77 | texte sur #D9D9D9 et bandes profondes |
| --info | #004B69 | mesuré | Fond de badge information, texte #FFFFFF | 9,50 | sur bandes profondes (2,21) |
| --link | #007864 + soulignement | règle | Lien en ligne | voir § 3.5 | couleur seule |
| --link-visited | #004B69 | mesuré | Lien visité — doit rester conforme | 9,50 | toute teinte non conforme |
Deux seuls neutres nouveaux sont nécessaires (#5F5F5F, #767676). Ce ne sont pas des couleurs
de marque : ce sont des neutres de lisibilité, exigés par SC 1.4.3 et SC 1.4.11. Ils n'augmentent
pas la palette (7 couleurs + 4 neutres + 2 jetons d'accessibilité) et doivent être documentés comme tels.
[À VALIDER CLIENT — arbitrage charte : accepter 2 neutres de lisibilité, ou accepter des échecs AA.]
3.2 Ce qu'il faut retirer du socle livré
- --tero-drift-sage (#97AB83) et toute la famille « drift » : à sortir de :root, préfixées
--x-drift-, dans un fichier de résorption, jamais dans les jetons distribués. [RÈGLE]
- --tero-muted rebaptisé --text-disabled : le nom « muted » a produit l'erreur F2.
Un nom qui dit « désactivé » empêche l'usage en placeholder. [RÈGLE]
3.3 Focus visible — le double anneau (correction de F1)
Construction conforme partout, sans dépendre du fond :
:root{
--focus-ring:#000000; / anneau extérieur /
--focus-halo:#FFFFFF; / gorge : reprend le fond de la page /
--focus-width:2px;
--focus-offset:2px;
}
.surface-inverse,[data-theme="dark"]{
--focus-ring:#FFFFFF; --focus-halo:#000000;
}
:where(a,button,input,select,textarea,summary,[tabindex]):focus-visible{
outline:var(--focus-width) solid var(--focus-ring);
outline-offset:var(--focus-offset);
box-shadow:0 0 0 calc(var(--focus-offset) + var(--focus-width)) var(--focus-halo);
}
@media (forced-colors:active){
:focus-visible{ outline:3px solid Highlight; }
}
Preuve de conformité : de l'intérieur vers l'extérieur, bouton teal sur section blanche →
teal/halo blanc = 5,43:1 ✅ → halo blanc/anneau noir = 21,00:1 ✅. Bouton noir → noir/halo
blanc 21,00 ✅ → blanc/anneau noir 21,00 ✅. Badge sable sur bande noire → sable/halo noir
14,61 ✅ → noir/anneau blanc 21,00 ✅. Aucun cas d'échec possible.
Compléments opposables (SC 2.4.11 / 2.4.12 / 2.4.13) :
- outline-offset ≥ 2 px et épaisseur ≥ 2 px : anneau ≥ 2 px de périmètre (2.4.13).
- Aucun overflow:hidden sur un ancêtre d'élément focalisable sans padding suffisant : l'anneau
serait rogné.
- scroll-margin-block-start: max(6rem, hauteur du header collant + 1rem) sur tout élément
focalisable : un en-tête collant ne doit jamais recouvrir l'élément focalisé (2.4.11), et
scroll-padding-block-start: 6rem seul ne suffit pas pour le focus clavier.
- Retour de focus obligatoire : fermeture de menu/modale → focus rendu à l'ouvreur.
- Piège de focus dans les modales (inert sur le reste du document), échappable par Échap.
- Ne jamais utiliser outline:none ni outline:0 sans remplacement équivalent (interdit § 7.1).
3.4 États — quelles couleurs peuvent porter un état
| État | Fond | Texte | Ratio | Emploi autorisé | Interdit |
|---|---|---|---|---|---|
| Succès | #007864 (teal) | #FFFFFF | 5,43 AA | Badge, bandeau, encart de confirmation | teal en couleur de texte d'état (collision avec le lien teal) |
| Avertissement | #FAD282 (sable) | #000000 | 14,61 AAA | Badge, encart, bordure gauche 4 px | sable en couleur de texte (1,44 sur blanc) |
| Erreur | #B93815 (terracotta) | #FFFFFF | 5,77 AA | Badge, encart, texte d'erreur de champ sur fond clair | terracotta en texte sur #D9D9D9 (4,09) et sur bande profonde (3,64 / 2,79) |
| Information | #004B69 (pétrole) | #FFFFFF | 9,50 AAA | Badge, encart, bandeau neutre | pétrole sur bande profonde (2,21) |
| Désactivé | #E5E6E5 | #B9B9B9 | 1,57 | Composant inactif uniquement (exempt) | tout composant actif |
| Chargement | — | — | — | aria-busy="true" + libellé « Envoi… » | spinner sans texte |
| Sélection | #F6F4F4 + bordure #767676 | #000000 | 19,17 | Onglet actif, option retenue | couleur seule (1.4.1) |
| Erreur sur bande profonde | #B93815 | #FFFFFF | 5,77 | Badge uniquement | terracotta en texte ou icône sur #000/#212121 |
Trois règles d'état opposables [RÈGLE] :
- Interdiction absolue : #FAD282, #D7B4B4, #00B7FF, #FF1D58, #B9B9B9, #D9D9D9 ne portent
jamais un état en couleur de texte (aucune ne passe 4,5:1 sur fond clair). Elles ne peuvent
être que le fond d'un badge à texte noir.
- Le teal porte deux sens (lien/accent et succès). Acceptable, mais : un succès s'exprime
toujours par icône + texte + couleur — jamais par la couleur seule. Sinon le lecteur ne peut
pas distinguer un lien d'un succès.
- Aucune couleur de dérive ne porte un état. #B00000 (7,38 sur blanc, conforme) est
interdit : il appartient à la dérive ; l'erreur est terracotta #B93815.
#FF4A4A (3,32 sur blanc) est non conforme en plus d'être une dérive.
SC 1.4.1 — l'information ne passe jamais par la couleur seule : tout état porte une icône
distincte et un libellé explicite (« Erreur : le téléphone doit être au format +32 4XX XX XX XX »).
3.5 Liens en ligne — la règle du soulignement
Teal #007864 contre le texte noir #000000 = 3,87:1 (≥ 3:1, technique G183 satisfaite).
Mais G183 exige un indice supplémentaire au survol/focus. [RÈGLE] :
- Tout lien en ligne dans un paragraphe est souligné en permanence (text-decoration-thickness
1 px, underline-offset 0,15em). C'est la seule solution non ambiguë pour un daltonien et pour le
mode contraste élevé.
- Une pastille/bouton portant un lien n'a pas besoin de soulignement (forme + libellé).
- :visited : #004B69 (9,50) — jamais une teinte non conforme, jamais la seule différence.
- Interdiction de la couleur seule pour distinguer un lien (SC 1.4.1).
3.6 Contraste élevé / forced-colors / préférences
| Préférence | Règle | Mise en œuvre |
|---|---|---|
| forced-colors: active (Windows contraste élevé) | Les icônes utilisent currentColor, les bordures passent par border (jamais background-image), les états ne reposent pas sur une image | media query dédiée + border-color: CanvasText |
| prefers-contrast: more | --border et --line passent à #000000 ; l'accent passe à #004B69 | media query |
| prefers-reduced-motion: reduce | Aucune animation, aucun zoom, aucune vidéo chargée (poster seul) | media query + JS |
| color-scheme | color-scheme: light explicite sur :root tant que le mode sombre n'existe pas (évite l'assombrissement natif des champs) | :root |
| Zoom 200 % | Aucune perte d'information, aucun défilement horizontal à 200 % sur 1280 px | test obligatoire |
| Zoom texte seul | text-size-adjust: 100% et aucune hauteur de texte figée en px | CSS |
---
4. Checklist de conformité WCAG 2.2 — testable
4.1 Les 9 critères ajoutés par WCAG 2.2 (aujourd'hui absents des règles)
| SC | Niv. | Exigence | Application TERO | Test |
|---|---|---|---|---|
| 2.4.11 Focus non masqué (minimum) | AA | L'élément focalisé n'est pas entièrement caché | Header collant, bandeau cookies, barre de réservation | Tab jusqu'au dernier élément du header : le focus doit rester visible |
| 2.4.12 Focus non masqué (renforcé) | AAA | Aucune partie cachée | Idem, visé | Idem, contrôle visuel |
| 2.4.13 Apparence du focus | AAA | Anneau ≥ 2 px de périmètre, contraste ≥ 3:1 | Double anneau § 3.3 | Mesure des ratios, 8 contextes |
| 2.5.7 Mouvements de glissement | AA | Alternative au glisser-déposer sur un seul pointeur | Carrousels, galeries, sélecteurs de date, cartes de lieux | Boutons précédent/suivant présents et clavier |
| 2.5.8 Taille de cible (minimum) | AA | ≥ 24 × 24 px CSS, ou espacement suffisant (exception : lien dans une phrase) | La règle groupe de 44 px dépasse l'exigence : à conserver | Mesure DOM de toutes les cibles ; 44 px au doigt |
| 3.2.6 Aide cohérente | A | Aide (contact, FAQ, chat) au même endroit relatif sur chaque page | Bloc « nous revenons vers vous endéans les 48 h » + contact | Comparaison sur 5 pages |
| 3.3.7 Saisie redondante | A | Ne pas redemander une information déjà saisie | Devis, réservation multi-étapes | Parcours clavier complet |
| 3.3.8 Authentification accessible | AA | Pas de test cognitif ; collage autorisé ; alternative au CAPTCHA | Formulaires, espaces partenaires | Coller un mot de passe, collage d'un code de vérification |
| 3.3.9 Authentification accessible (renforcée) | AAA | Pas de reconnaissance d'objets | Idem, visé | Contrôle manuel |
4.2 Checklist AA — par famille, avec la preuve exigée
A. Perceptible
| # | Critère | Exigence TERO | Preuve exigée |
|---|---|---|---|
| A1 | 1.1.1 Contenu non textuel | Alt réel et factuel ; alt vide uniquement pour le décoratif ; le monogramme tero a pour alt « tero » | Export du listing alt/texte du DOM, 0 alt générique (« image », nom de fichier) |
| A2 | 1.2.2 Sous-titres | Toute vidéo avec son : sous-titres FR et une piste par langue publiée + transcription | Fichier .vtt livré, test lecteur |
| A3 | 1.2.5 Audiodescription | Si l'information visuelle est essentielle | Décision documentée par vidéo |
| A4 | 1.3.1 Information et relations | Structure sémantique : header/nav/main/aside/footer, listes réelles, tableaux avec th scope, exactement 1 H1, aucun saut de niveau | Audit axe + extraction des titres (le live en compte 2 H1, 0 H2) |
| A5 | 1.3.5 Identification des champs | autocomplete sur nom, e-mail, téléphone, adresse, société | Revue DOM des formulaires |
| A6 | 1.4.3 Contraste (minimum) | § 2.2 et § 2.4 à la lettre ; placeholder ≥ 4,5:1 ; jamais de placeholder comme label | Matrice de contraste versionnée dans le dépôt |
| A7 | 1.4.4 Redimensionnement | 200 % de zoom sans perte ni défilement horizontal | Capture à 200 % sur 1280 px |
| A8 | 1.4.10 Réadaptation | 320 px sans défilement horizontal (2 dimensions) | Capture 320 px, 0 débordement |
| A9 | 1.4.11 Contraste non textuel | Bordures de contrôles ≥ 3:1 (#767676), anneaux ≥ 3:1, icônes porteuses de sens ≥ 3:1 | Mesure : 6 couples minimum |
| A10 | 1.4.12 Espacement du texte | Interligne 1,5× ; espacement de paragraphe 2× ; lettrage 0,12em ; mots 0,16em — appliqués par feuille utilisateur, sans perte | Test avec CSS de surcharge |
| A11 | 1.4.13 Contenu au survol/focus | Infobulles : fermables par Échap, survolables, persistantes | Test clavier et souris |
| A12 | 1.4.5 / 1.4.11 Images de texte | Aucune image de texte porteuse d'information (hors logo) | Revue |
| A13 | prefers-contrast / forced-colors | § 3.6 | Capture en contraste élevé Windows |
B. Utilisable
| # | Critère | Exigence TERO | Preuve exigée |
|---|---|---|---|
| B1 | 2.1.1 Clavier | Tout est atteignable et activable au clavier ; le survol a un équivalent focus | Parcours clavier complet enregistré (accueil → lieu → formulaire) |
| B2 | 2.1.2 Pas de piège clavier | Modales fermables par Échap, focus rendu | Test |
| B3 | 2.2.1 / 2.2.2 Temps | Aucune limite de temps ; sinon extensible | Revue |
| B4 | 2.3.1 / 2.3.3 Clignotement et mouvement | Aucun clignotement > 3 Hz ; animations neutralisées sous prefers-reduced-motion ; vidéo non chargée en mouvement réduit | Test en préférence système |
| B5 | 2.4.1 Contourner les blocs | Lien d'évitement visible au focus, en tête, avant le header | Tab depuis le chargement : premier arrêt = « Aller au contenu » |
| B6 | 2.4.2 Titre de page | Titre unique, explicite, ≤ 65 caractères, distinct par langue | Extraction de tous les title |
| B7 | 2.4.3 Ordre de focus | Ordre identique au sens de lecture ; pas de tabindex positif | Audit DOM (0 tabindex > 0) |
| B8 | 2.4.4 / 2.4.9 Intitulé du lien | Intitulé explicite hors contexte ; « en savoir plus » interdit seul | Audit des liens |
| B9 | 2.4.6 En-têtes et étiquettes | Titres descriptifs, labels explicites, 1 H1, ordre sans saut | Extraction des titres |
| B10 | 2.4.7 Focus visible | Anneau du § 3.3, jamais supprimé | Capture des 8 contextes |
| B11 | 2.4.11 (2.2) | § 4.1 | Test header collant |
| B12 | 2.5.1 / 2.5.2 Gestes et annulation | Aucun geste multipoint obligatoire ; l'action se déclenche au relâchement et peut être annulée | Revue |
| B13 | 2.5.3 Intitulé dans le nom | Le texte visible est contenu dans le nom accessible | Audit (bouton « Réserver » avec aria-label différent = échec) |
| B14 | 2.5.4 Action par mouvement | Aucune action déclenchée par secousse/inclinaison | Revue |
| B15 | 2.5.7 / 2.5.8 (2.2) | § 4.1 | Mesure DOM des cibles |
| B16 | 2.1.4 Raccourcis clavier | Aucun raccourci à une touche non désactivable | Revue |
| B17 | Langue de la page | lang par page : fr-BE / nl-BE / en-GB (aujourd'hui fr-FR sur un site belge) | Extraction du html lang sur 3 langues |
C. Compréhensible
| # | Critère | Exigence TERO | Preuve exigée |
|---|---|---|---|
| C1 | 3.1.1 Langue de la page | Conforme et cohérent avec hreflang (fr, nl, en) | Extraction |
| C2 | 3.1.2 Langue d'un passage | lang sur tout passage dans une autre langue (noms propres exclus) | Audit |
| C3 | 3.2.1 / 3.2.2 Contexte | Aucun changement de contexte au focus ou à la saisie | Test |
| C4 | 3.2.3 / 3.2.4 Navigation cohérente | Ordre de navigation identique sur toutes les pages et toutes les langues | Comparaison de 5 pages × 3 langues |
| C5 | 3.2.6 (2.2) Aide cohérente | § 4.1 | Comparaison |
| C6 | 3.3.1 Identification des erreurs | L'erreur est textuelle, liée au champ (aria-describedby), aria-invalid="true", cause et correction | 1 formulaire testé par type |
| C7 | 3.3.2 Étiquettes et instructions | Label visible et lié ; jamais le placeholder pour label ; format attendu annoncé | Revue DOM |
| C8 | 3.3.3 Suggestion après erreur | Exemple conforme : « Le téléphone doit être au format +32 4XX XX XX XX » | Capture |
| C9 | 3.3.4 Prévention (financier) | Récapitulatif, révision, confirmation avant paiement/réservation engageante | Parcours |
| C10 | 3.3.7 / 3.3.8 / 3.3.9 (2.2) | § 4.1 | Parcours clavier |
D. Robuste
| # | Critère | Exigence TERO | Preuve exigée |
|---|---|---|---|
| D1 | 4.1.2 Nom, rôle, valeur | Composants maison (accordéon, onglets, carrousel, menu) : rôle et états ARIA corrects | Audit axe + test lecteur d'écran |
| D2 | 4.1.3 Messages d'état | Erreur, succès, chargement annoncés par role="status" / role="alert" / aria-live | Test NVDA/VoiceOver |
| D3 | Lecteurs d'écran | Parcours complet NVDA + Firefox (Windows) et VoiceOver + Safari (macOS/iOS) | Compte rendu de test daté |
| D4 | Zoom texte seul | 200 % sans perte | Capture |
4.3 Portes CI obligatoires (à ajouter à § 16.2, aujourd'hui 0/8)
| Porte | Outil | Seuil de refus |
|---|---|---|
| P1 | axe-core (ou @axe-core/cli / playwright-axe) sur les 5 gabarits × 3 langues | 0 violation de niveau serious/critical |
| P2 | Lighthouse (accessibilité) | 100 sur chaque gabarit |
| P3 | Comptage de couleurs / tailles / rayons introduits | Toute valeur hors § 2.1/2.2 et hors rampe → échec |
| P4 | Matrice de contraste générée et versionnée | Tout couple < 4,5:1 (texte) ou < 3:1 (non-texte) → échec |
| P5 | Budget de poids (size-limit / Lighthouse CI budgets) | CSS > 45 Ko brotli, JS > 70 Ko brotli, polices > 54 Ko → échec |
| P6 | Structure de titres | ≠ 1 H1, ou saut de niveau → échec |
| P7 | Largeurs de capture | 320 / 360 / 390 / 768 / 1024 / 1440 — 0 débordement horizontal |
| P8 | tabindex positif, outline: none, aria-label divergent du texte visible | Échec |
4.4 Anti-patterns d'accessibilité à ajouter à DESIGN.md § 9
- Supprimer ou affaiblir le focus pour des raisons esthétiques.
- Utiliser un placeholder comme étiquette de champ.
- Signaler un état (erreur, succès, sélection) par la couleur seule.
- Poser du texte sur un fond qui n'atteint pas le seuil de contraste de son niveau.
- Exposer un jeton de dérive (--tero-drift-sage) dans le socle livré.
---
5. Performance — budget mesurable et pièges mesurés
5.1 Ce que le site sert aujourd'hui — [MESURE 15/10/2026]
| Mesure | Valeur | Méthode |
|---|---|---|
| Corpus CSS Elementor | 536 feuilles, 9,1 Mo | audit § 3.5 |
| Feuilles CSS déclarées sur l'accueil | ≥ 34 (HTML tronqué à 100 ko) | comptage des link rel=stylesheet |
| Poids de 10 de ces feuilles, compressées (gzip) | 170,5 Ko | requêtes réseau réelles |
| dont jet-engine frontend.css | 62,7 Ko (36,8 %) | — |
| dont elementor frontend.min.css | 54,7 Ko (32,1 %) | — |
| dont mega-menu Elementor Pro | 19,9 Ko | — |
| dont post-4246.css | 15,3 Ko | — |
| dont post-6.css — le kit de palette/typo du groupe | 3,8 Ko | le socle de marque pèse 2 % du CSS |
| dont jet-theme-core frontend.css | 0 octet (fichier servi vide) | requête inutile |
| Scripts externes | 10 (dont jQuery 3.7.1 + jquery-migrate 3.4.1, Cookiebot, Brevo SDK, WonderPush) + 12 inline | extraction |
| Polices | 4 woff2 (400/500/600/700), font-display: auto, 0 préchargement | @font-face + link rel=preload |
| Poids des 4 graisses Almarose | 18,0 + 17,9 + 18,1 + 16,2 = 70,2 Ko | fichiers du miroir local |
| Images (100 premiers ko) | 9 img, 0 loading=lazy, 0 srcset, 9/9 avec width+height ✅ | extraction |
| Indices de préconnexion | 19 dns-prefetch, 0 preconnect | sur-déclaration inutile |
| Styles inline | 14 blocs style dans les 100 premiers ko | extraction |
| og:image | annoncé 800 × 534, servi 1024 × 683 | audit § 5.5, confirmé |
| Structure de titres (100 premiers ko) | 2 H1, 0 H2, 0 H3 | extraction |
| lang | fr-FR (site belge, hreflang fr) | extraction |
Lecture : la dette n'est pas la marque — le kit de palette pèse 3,8 Ko. La dette est
le framework (jet-engine + Elementor + mega-menu = 137 Ko compressés pour 10 feuilles sur ≥ 34).
Le socle TERO peut et doit viser ≤ 45 Ko brotli tout compris, soit un tiers de ce que sert
aujourd'hui une fraction de l'accueil. [RÈGLE]
5.2 Budget cible — à inscrire dans § 11
| Ressource | Plafond | Mesure actuelle | Contrôle |
|---|---|---|---|
| CSS (brotli, 1re visite, toutes feuilles) | ≤ 45 Ko · ≤ 3 requêtes · 1 seul fichier bloquant | ≥ 170,5 Ko pour 10 des ≥ 34 feuilles | size-limit / Lighthouse CI budgets |
| JS (brotli) | ≤ 70 Ko · ≤ 4 requêtes · 0 jQuery | 10 scripts externes + jQuery + migrate | Bundle analyzer |
| Polices | ≤ 3 woff2 = 54 Ko · 1 seul preload (graisse du H1) · font-display: swap · size-adjust | 4 woff2 = 70,2 Ko, display auto, 0 preload | Requête réseau + capture LCP |
| Images | Hero ≤ 150 Ko AVIF · vignette ≤ 40 Ko · srcset + sizes sur 100 % des images | 0 srcset mesuré, 1024 × 683 annoncé 800 × 534 | Audit d'images |
| Requêtes totales | ≤ 35 | ≥ 34 CSS + 10 JS + tiers | DevTools / WebPageTest |
| Tiers | 0 requête tierce avant consentement | Cookiebot + Brevo + WonderPush chargés | Analyse réseau à froid |
| TTFB | ≤ 500 ms p75 mobile | non mesuré | CrUX / WebPageTest |
| LCP | ≤ 1,5 s p75 mobile terrain | non mesuré | CrUX, pas Lighthouse labo |
| INP | ≤ 200 ms p75 mobile terrain | non mesuré | CrUX |
| CLS | ≤ 0,1 (viser ≤ 0,02 avec repli métrique) | non mesuré | CrUX |
| Cache | immutable, max-age=31536000 sur tout asset haché | à vérifier | En-têtes |
| Compression | brotli (gzip en repli) | gzip servi | En-têtes |
| TBT / tâches longues | ≤ 200 ms · aucune tâche > 100 ms avant interaction | — | Lighthouse |
Précision importante [RÈGLE] : la cible LCP < 1,5 s du groupe est plus stricte que le
seuil « bon » de Google (≤ 2,5 s). Elle n'est opposable que si elle est mesurée **au p75 sur le
terrain (CrUX / RUM)**, pas en laboratoire. Sinon elle devient un chiffre invérifiable. À inscrire
explicitement : « LCP ≤ 1,5 s au p75 mobile, données terrain, calculées sur 28 jours ».
5.3 Polices — le levier le plus rentable
- 3 graisses suffisent : 400 (corps), 600 (emphase et CTA), 700 (titres). Le 500 n'est
conservé que si un usage réel est mesuré ; sinon il sort du socle (−18 Ko). [RÈGLE]
- Sous-ensemblage latin + latin-ext (accents français et néerlandais : é è ê ë à â ç ï ô û ü,
ij, ij) ; jamais de sous-ensemble latin seul sur un site FR/NL/EN.
- font-display: swap (aujourd'hui auto = jusqu'à 3 s de texte invisible).
- 1 seul preload, celui de la graisse du H1 (700). Précharger 2 graisses met 36 Ko en
concurrence de la photo — c'est le LCP sur tero.be. [RÈGLE]
- Repli métrique obligatoire pour annuler le décalage de swap :
@font-face{font-family:"Almarose Fallback";src:local("Arial");size-adjust:…;ascent-override:…;}
Les valeurs se mesurent (une fois) avec le font-metrics tool, puis se figent. [RÈGLE]
- Aucune police tierce (Google Fonts) : Almarose est déjà auto-hébergée ✅.
- WildYouth : si le client la garde, elle devient une 4e requête et doit être en
font-display: swap, préchargée uniquement sur les pages qui l'utilisent. Sans arbitrage :
non chargée.
5.4 Images et LCP
- Le LCP de tero.be est une photo. Interdits : background-image pour le hero (non
préchargeable, non indexable), loading="lazy" sur le hero, image redimensionnée par le CSS.
- Hero : AVIF (repli WebP), largeur exacte du viewport, fetchpriority="high", préchargé,
≤ 150 Ko.
- srcset 480 / 768 / 1024 / 1440 / 1920 + attribut sizes : un srcset sans sizes ne sert
à rien. Mesure de référence sur le projet padel : 1 209 Ko d'images sur une page en 390 px
avec une seule taille de fichier par image. [MESURE — projet padel, non transposée au groupe]
- width + height (ou aspect-ratio) sur toutes les images : le site groupe le fait déjà
(9/9 mesurées) ✅ — c'est à maintenir dans le socle.
- loading="lazy" sur tout ce qui est sous le premier écran (aujourd'hui 0/9) et
decoding="async" hors LCP.
- Formats : AVIF d'abord, WebP en repli, JPEG en dernier recours ; nommage
tero-entite-sujet-largeur.ext (§ 15) conservé.
5.5 INP et JavaScript
- Supprimer jQuery et jquery-migrate : inutiles sur un socle moderne (−30 Ko compressés,
−1 requête bloquante).
- Aucun tiers avant consentement : Cookiebot, Brevo, WonderPush sont aujourd'hui chargés sur
l'accueil. Un SDK de push (WonderPush) est un coût d'INP et un sujet RGPD — à trancher.
[À VALIDER CLIENT]
- Budget de script : ≤ 4 requêtes, chacune ≤ 25 Ko ; tout script non critique en defer.
- Aucune tâche principale > 100 ms avant la première interaction ; animations en CSS ou en
requestAnimationFrame court.
- Les composants interactifs (menu, accordéon, carrousel) ne doivent jamais bloquer le fil
principal : content-visibility sous le pli, IntersectionObserver plutôt que scroll listener.
- Un module = un point d'entrée (piège mesuré sur le projet padel : script initialisé deux
fois, écouteurs posés en double → INP et focus dégradés).
5.6 CLS
- Repli métrique des polices (§ 5.3) — le décalage de swap est la première cause de CLS.
- width/height ou aspect-ratio sur 100 % des médias, y compris les iframes (YouTube).
- Bandeau cookies en superposition (position: fixed), jamais inséré dans le flux.
- Aucune insertion de contenu au-dessus du pli après chargement (bandeaux promo, « X personnes
consultent »).
- Espace réservé pour les blocs dynamiques (formulaire, carte, témoignage).
5.7 Protocole de mesure opposable
| Vérification | Commande / outil | Preuve à joindre |
|---|---|---|
| Poids et budget | Lighthouse CI avec assert.budgets (resourceSizes / resourceCounts) | Rapport JSON daté |
| Web Vitals terrain | PageSpeed Insights API (CrUX) p75 mobile | Capture 28 jours |
| Poids réel des polices/images | Onglet réseau, filtre Font/Img | Capture |
| Tiers | Liste des domaines tiers de la 1re visite | Capture |
| Compression | En-têtes content-encoding | Capture |
| Structure | axe + extraction des titres | Rapport JSON |
| Contraste | Matrice générée par script (§ 2.1) | Fichier versionné |
---
6. Mode sombre — décision et construction
Recommandation : ne pas livrer de thème sombre global au lancement. [RÈGLE proposée]
Motifs : (1) aucun signal de marque ni de demande mesurée ; (2) le site groupe est **noir et blanc
sur fond blanc, la « bande profonde » existe déjà comme variante de section** ; (3) un thème
sombre double la matrice de contraste, la campagne de captures et la QA images.
[À VALIDER CLIENT — question à trancher, avec le coût.]
Trois choses sont néanmoins obligatoires dès maintenant [RÈGLE] :
- color-scheme: light explicite sur :root, sinon les navigateurs en préférence sombre
assombrissent nativement champs, listes déroulantes et barres de défilement — sans contrôle.
- Formaliser la bande profonde comme variante de surface, pas comme cas particulier :
.surface-inverse porte --surface: #000000, --surface-alt: #212121, --text: #FFFFFF,
--text-secondary: #B9B9B9, --border: #767676, --accent: #FAD282, --focus-ring: #FFFFFF,
--focus-halo: #000000. Le jour où le client dit oui, le thème sombre devient un **échange de
jetons**, pas une refonte.
- Le logo blanc existe (logo-tero-white.svg) ✅, et les photos sur fond noir exigent un voile
(déjà prévu § 4.2.5).
Si le client valide le mode sombre, la matrice à respecter est la suivante (colonnes #000000 et
#212121 du § 2.3) : texte #FFFFFF (21,00 / 16,10), secondaire #B9B9B9 (10,70 / 8,21), accent
#FAD282 (14,61 / 11,20) ou #00B7FF (9,22 / 7,07), bordure #767676 (4,62 / 3,55), erreur = badge
terracotta + texte blanc (5,77). Interdits en sombre : pétrole (2,21 / 1,69), teal en texte
(3,87 / 2,97), terracotta en texte (3,64 / 2,79), encre douce (1,64 / 1,26).
Coût à annoncer : **+1 matrice de contraste, +6 captures par gabarit, +1 test lecteur d'écran,
+1 QA images** (≈ +15 à 20 % de la charge de recette). [RÈGLE]
---
7. Limites de cette étude
- Les mesures réseau portent sur le HTML tronqué à 100 ko de https://tero.be/fr/ : tous les
comptages d'assets sont des minorants (≥ 34 feuilles CSS, ≥ 10 scripts).
- Les ratios sont calculés sur les valeurs hexadécimales du CSS servi, pas sur les pixels
rendus : une opacité, un voile photo ou un dégradé peut modifier le contraste réel. La
vérification au navigateur reste obligatoire (§ 5.7).
- Le contraste réel d'un texte sur photo ne peut pas être calculé sans échantillonnage : la
règle « version blanche + voile sombre » (§ 4.2.5) doit être mesurée au pixel sur chaque
visuel portant du texte.
- Le poids de la page d'accueil complète n'a pas pu être mesuré en une passe (protection
anti-bot : HTTP 403 hors navigateur) : à refaire en WebPageTest ou Lighthouse CI.
- Les performances terrain (CrUX p75) ne sont pas disponibles dans cet audit : les cibles
LCP/INP/CLS restent à instrumenter avant d'être opposables.
- Aucune donnée d'usage réel (analytics) n'a été consultée : la recommandation « pas de mode
sombre » repose sur l'absence de demande mesurée, pas sur un comptage d'utilisateurs.
---
DELTA A INTEGRER DANS LA CHARTE
- Corriger la casse du mot-symbole dans tero-group-rules.md:257, 276, 292, DESIGN.md § 8 et
design-tokens.json (brand.wordmarkCase) : le mot-symbole officiel est tero, en minuscules
(_audit/logo-tero-2025.svg) ; Tero = graphie rédactionnelle. Remplacer toute mention de
« mot-symbole TERO ».
- Corriger la description du monogramme (tero-group-rules.md:262 et design-tokens.json
brand.logoForbidden) : le glyphe est le o stylisé (anneau plein + barre horizontale sous
l'anneau) ; il reste interdit comme Organization.logo, mais devient utilisable en pastille avec
alt = « tero ».
- Ajouter --tero-ink-muted: #5F5F5F (neutre de lisibilité) et redéfinir **--tero-muted
#B9B9B9 comme jeton « désactivé » uniquement** ; interdire #B9B9B9 pour placeholders, libellés
et mentions (SC 1.4.3, preuve : 1,96:1).
- Ajouter --tero-border-field: #767676 et une règle : « toute bordure de contrôle atteint 3:1
contre ses fonds adjacents ; #D9D9D9 et #E5E6E5 sont décoratifs et ne délimitent jamais un
contrôle » (SC 1.4.11, preuve : 1,41:1 et 1,13:1).
- Remplacer --focus-ring teal par le double anneau (--focus-ring #000000 / --focus-halo
#FFFFFF sur fonds clairs ; inverse sur bandes profondes), avec les 6 compléments de § 3.3
(épaisseur ≥ 2 px, offset ≥ 2 px, pas de rognage, scroll-margin, retour de focus, inert).
- Trancher la contradiction du teal en bande profonde (§ 7.3 vs DESIGN.md § 6 vs
design-tokens.json) : teal interdit sur #212121 (2,97:1) ; sur #000000 **graphique
décoratif uniquement** (3:1), jamais en texte.
- Ajouter la règle des trois familles d'aplats (§ 2.4) : famille A (teal, pétrole, terracotta)
= texte blanc ; famille B (sable, rose poudré, cyan, rose vif, #B9B9B9, #D9D9D9) = texte noir,
jamais en couleur de texte sur fond clair.
- Interdire teal en texte sur #E5E6E5 (4,34) et #D9D9D9 (3,84) ; **interdire terracotta en
texte sur #D9D9D9 (4,09) ; interdire pétrole sur toute bande profonde (2,21 / 1,69)**.
- Corriger le seuil « grand texte » (tero-group-rules.md:600) : 3:1 pour ≥ 24 px ou
≥ 18,66 px en graisse 700 — et non « ≥ 24 px gras ».
- Ajouter la couche sémantique complète de § 3.1 (surface, surface-alt, surface-elevated,
surface-inverse, text, text-secondary, text-muted, text-disabled, text-inverse,
text-inverse-secondary, border, border-field, border-inverse, accent, accent-hover,
accent-on-dark, focus-ring, focus-halo, success, warning, error, info, link, link-visited) à
tokens.css et design-tokens.json.
- Supprimer --tero-drift-sage et la famille drift du :root livré : les préfixer --x-drift- et
les isoler dans un fichier de résorption (un jeton exposé finit utilisé).
- Ajouter la table d'états § 3.4 : succès = teal + texte blanc ; avertissement = sable + texte
noir ; erreur = terracotta + texte blanc ; information = pétrole + texte blanc ; et
l'interdiction pour sable, rose poudré, cyan, rose vif, #B9B9B9, #D9D9D9 de porter un état en
couleur de texte.
- Ajouter la règle « l'information ne passe jamais par la couleur seule » : tout état porte
une icône distincte + un libellé (SC 1.4.1), et **tout lien en ligne dans un paragraphe est
souligné en permanence** (§ 3.5).
- Ajouter #FF2E82 (magenta padel) et #E9F44E (lime padel) à la liste des dérives de
design-tokens.json, avec renvoi à la question client n° 2 (preuves : 3,20:1 et 1,20:1 sur blanc).
- Ajouter le bloc WCAG 2.2 § 4.1 (les 9 nouveaux critères) à la section Accessibilité :
2.4.11, 2.4.12, 2.4.13, 2.5.7, 2.5.8, 3.2.6, 3.3.7, 3.3.8, 3.3.9, avec portée TERO et test.
- Remplacer la section Accessibilité (§ 10) par la checklist testable § 4.2 : elle est
aujourd'hui un résumé de 9 puces, sans preuve, sans vidéo, sans zoom, sans 320 px, sans
autocomplete, sans espacement du texte, sans contenu au survol, sans annulation de pointeur.
- Ajouter 320 px aux largeurs de contrôle et de capture (§ 8.2 et § 16.2) : la liste devient
320 / 360 / 390 / 768 / 1024 / 1440 (SC 1.4.10 Reflow).
- Ajouter les portes CI a11y et performance § 4.3 aux contrôles minimaux avant livraison
(§ 16.2), avec seuils de refus : axe = 0 violation, Lighthouse a11y = 100, 1 seul H1, matrice de
contraste versionnée, budget de poids, 0 tabindex positif, 0 outline: none.
- Remplacer la section Performance (§ 11) par le budget chiffré § 5.2 : CSS ≤ 45 Ko brotli /
≤ 3 requêtes, JS ≤ 70 Ko / ≤ 4 requêtes / 0 jQuery, polices ≤ 3 woff2 = 54 Ko avec **1 seul
preload**, images hero ≤ 150 Ko AVIF avec srcset + sizes, ≤ 35 requêtes, 0 tiers avant
consentement, TTFB ≤ 500 ms, cache immutable 1 an, brotli.
- Préciser la cible LCP : « ≤ 1,5 s au p75 mobile sur données terrain (CrUX), 28 jours » —
plus stricte que le seuil Google de 2,5 s, donc invérifiable en laboratoire seul.
- Imposer font-display: swap + repli métrique (size-adjust) + 1 preload, et documenter
l'écart mesuré : le site sert aujourd'hui font-display: auto × 4 et 0 preload.
- Limiter le socle à 3 graisses (400 / 600 / 700) : le 500 sort, sauf usage réellement mesuré
(−18 Ko ; les 4 graisses pèsent 70,2 Ko).
- Interdire jQuery et jquery-migrate dans le socle neuf, et documenter les tiers chargés
aujourd'hui sur l'accueil (Cookiebot, Brevo SDK, WonderPush) comme points à trancher avec le
client (RGPD + INP).
- Ajouter les 4 anti-patterns d'accessibilité à DESIGN.md § 9 : focus supprimé ; placeholder
utilisé comme label ; état signalé par la couleur seule ; texte posé sur un fond non conforme.
- Corriger le libellé de méthode dans design-tokens.json : « WCAG 2.2 AA, formule de luminance
relative W3C » (et non 2.1), et requalifier #323232 dans l'audit (§ 3.5) comme **neutre
adopté** par la charte, non comme dérive.
- Ajouter la règle de langue : lang="fr-BE" | "nl-BE" | "en-GB" cohérent avec les hreflang
fr / nl / en (le live sert fr-FR sur un site belge), et lang sur tout passage alloglotte.
- Ajouter la règle de réduction du mouvement couplée à la performance : sous
prefers-reduced-motion: reduce, aucune animation et la vidéo n'est pas chargée (poster
seul) — la règle existe sur le projet padel, elle manque au groupe.
- Ajouter la règle de sous-titres et transcription (SC 1.2.2 / 1.2.5) pour toute vidéo avec
son, dans une piste par langue publiée.
- Ajouter la règle du mode sombre § 6 : color-scheme: light explicite, .surface-inverse
formalisé comme jeu de jetons complet, et décision client explicite avant tout
développement de thème sombre (coût : +15 à 20 % de recette).
- Ajouter la règle de cible tactile en deux niveaux : 24 × 24 px minimum (SC 2.5.8 AA,
dépassé) et 44 × 44 px comme règle de marque, avec l'exception explicite du lien en ligne
dans une phrase (ne pas étirer le corps de texte pour atteindre 44 px).