Commutateur de thème d'interface
1) Principes
1. Système> peaux. Le thème n'est pas seulement une inversion d'arrière-plan, mais un ensemble de tokens (couleur, arrière-plan, contraste, ombres, états, illustrations, graphiques).
2. System-first. Par défaut, le système ('prefers-color-scheme') permet de sélectionner manuellement Light/Dark/High-Contrast.
3. Contraste par défaut. L'objectif est WCAG AA, pour les petits textes/étiquettes importantes - AAA.
4. Pas d'éclosions. Appliquer le thème avant le rendu (inline-script), et faire les transitions soigneusement.
5. Stabilité de la marque. Les accents et la sémantique des statuts sont conservés dans tous les thèmes.
2) Modes et scripts
Light - scripts journaliers/formulaires de paiement/lectures longues.
Dark - soirée/faible éclairage/match live ; réduit l'éblouissement.
Système - nous suivons le système d'exploitation/navigateur ('prefers-color-scheme').
High-Contrast - contraste accru et minimisation des bijoux (incl. reduce motion).
Seasonal/Promo (en option) - au-dessus du thème de base pour le tournoi/événement (ne brise pas les tokens).
3) Architecture de tokens
Nous stockons des jetons sémantiques plutôt que des couleurs droites :css
:root {
/ semantics/
--bg: hsl(0 0% 99%);
--bg-elev: hsl(0 0% 100%);
--fg: hsl(220 15% 15%);
--muted: hsl(220 10% 45%);
--accent: hsl (260 95% 60%) ;/brand accent/
--success: hsl(152 55% 40%);
--warning: hsl(36 85% 45%);
--danger: hsl(0 75% 50%);
--border: hsl(220 10% 90%);
--focus: hsl(260 95% 60% /.6);
--shadow: 0 6px 24px hsl(220 20% 10% /.08);
/ typography/radii/
--radius: 12px;
--lh: 1. 4;
}
:root[data-theme="dark"]{
--bg: hsl(220 18% 10%);
--bg-elev: hsl(220 18% 14%);
--fg: hsl(0 0% 96%);
--muted: hsl(220 10% 70%);
--accent: hsl (260 95% 65%) ;/slightly lighter in the dark/
--border: hsl(220 14% 22%);
--shadow: 0 8px 28px hsl(220 50% 2% /.6);
}
:root[data-theme="hc"]{
/ high-contrast: simple pairs, high Lc/
--bg: #000; --bg-elev:#000; --fg:#fff; --muted:#fff;
--accent:#00E; --success:#0F0; --warning:#FF0; --danger:#F00;
--border:#fff; --focus:#0FF;
}
Règle : les composants n'utilisent que des jetons sémantiques.
4) Détecteur et sauvegarde de la sélection
html
<script>
(function(){
const saved = localStorage. getItem('theme'); // 'light' 'dark' 'hc' 'system' null const sys = window. matchMedia('(prefers-color-scheme: dark)'). matches? 'dark': 'light';
const theme = saved && saved!=='system'? saved: sys;
document. documentElement. setAttribute('data-theme', theme);
})();
</script>
Commutateur UI : 'Light/Dark/System/High-Contrast'. Si vous sélectionnez « System », ne conservez pas une couleur spécifique, mais uniquement l'indicateur. Écoutez les changements d'OS :
js matchMedia('(prefers-color-scheme: dark)'). addEventListener('change', e=>{
if(localStorage. getItem('theme')==='system'){
document. documentElement. setAttribute('data-theme', e. matches? 'dark': 'light');
}
});
5) Transitions en douceur sans FOUC
Appliquez le thème avant de télécharger le CSS (inline-script).
Les animations du thème sont courtes et seulement 'color/background/border-color' (120-200 ms), mais pas au premier rendu :css
@media (prefers-reduced-motion: no-preference){
html. theme-ready { transition: color. 18s, background-color. 18s, border-color. 18s; }
}
Après avoir monté l'application, ajoutez 'class =' theme-ready '.
6) Composants et états
Texte/icônes : contraste ≥ AA ; le texte secondaire n'est pas inférieur à 4. 5:1 (en darka, il est facile de « fumer »).
Champs/cartes : Arrière-plan '--bg-elev', bordure '--border', ombre '---shadow'.
CTA : fond '--accent', texte contrasté ('# fff' ou calculé).
États (hover/focus/active/disabled) : modifiez la luminosité/alpha plutôt que de « déborder l'arc-en-ciel ».
Graphiques/sparklines : palettes séparées pour light/dark ; les mailles/axes sont peu contrastés mais lisibles.
7) Images/médias/logos
Les icônes sont monochromes - via 'currentColor' (ajustées au texte).
N'inversez pas les logos des marques ; préparez deux versions (light/dark).
Affiches/captures d'écran : facile overlay en darka (8-12 %) pour la lisibilité des textes.
SVG : évitez les remplissage « rigides », utilisez « var (--fg) »/« var (--accent) ».
8) Disponibilité
Contraste élevé : preset séparé » data-theme = » hc'.
Anneaux de focus : toujours visibles ('outline : 2px solid var (--focus) ; outline-offset: 2px`).
Ne comptez pas sur la couleur. Icône/texte/modèle pour les statuts.
Polices : 'font-variant-numeric : tabular-nums ;' pour les montants/coefficients.
RTL : le thème ne brise pas la mise en miroir (on utilise des propriétés logiques).
9) Performance
Couleurs - CSS custom properties à la racine → une réutilisation instantanée sans rerender de composants.
Évitez de « repeindre » les images avec les filtres 'invert ()' sur les grands conteneurs.
Échange paresseux d'illustrations lourdes pour le sujet (si nécessaire).
Ne stockez pas de grandes palettes dans JS - le thème est contrôlé par classe/attribut.
10) Spécificité iGaming
La nuit, le contraste « doux » (AAA pour les nombres), la mise en surbrillance du changement de coefficient est discrète, sans scintillement.
Jeu responsable : les rappels et les indices sont lisibles dans les deux thèmes ; pas de fleurs « toxiques » la nuit.
Caisse : champs/signatures/erreurs sans accents lumineux fatigants ; succès/erreur sont stables par sujet.
Tournois « peaux » : seulement en tant que surface accent-override, ne cassez pas les jetons de base.
11) Snappets UI
Commutateur (HTML/JS) :html
<label class="theme-switch">
<span> Topic </span>
<select id="theme">
<option value = "system "> System </option>
<option value = "light "> Bright </option>
<option value = "dark "> Dark </option>
<option value = "hc"> High contrast </option>
</select>
</label>
<script>
const sel = document. getElementById('theme');
sel. value = localStorage. getItem('theme') 'system';
sel. addEventListener('change', e=>{
const v = e. target. value;
localStorage. setItem('theme', v);
if(v==='system'){
const sys = matchMedia('(prefers-color-scheme: dark)'). matches? 'dark':'light';
document. documentElement. setAttribute('data-theme', sys);
} else {
document. documentElement. setAttribute('data-theme', v);
}
});
</script>
Presets de composants :
css
.btn{height:44px; padding:0 16px; border-radius:var(--radius); display:inline-flex; align-items:center; gap:8px}
.btn--primary{background:var(--accent); color:#fff}
.card{background:var(--bg-elev); border:1px solid var(--border); box-shadow:var(--shadow); border-radius:var(--radius)}
.text-muted{color:var(--muted)}
React hook (persist + system):
tsx import { useEffect, useState } from 'react';
type Theme = 'light' 'dark' 'hc' 'system';
export function useTheme(){
const [theme, setTheme] = useState<Theme>(()=>localStorage. getItem('theme') as Theme 'system');
useEffect(()=>{
const apply = (t:Theme)=>{
const v = t==='system'? (matchMedia('(prefers-color-scheme: dark)'). matches? 'dark':'light'): t;
document. documentElement. setAttribute('data-theme', v);
};
apply(theme);
const mql = matchMedia('(prefers-color-scheme: dark)');
const on = ()=> theme==='system' && apply('system');
mql. addEventListener('change', on);
return ()=> mql. removeEventListener('change', on);
},[theme]);
useEffect(()=> localStorage. setItem('theme', theme),[theme]);
return { theme, setTheme };
}
12) Métriques
Taux d'adaptation : part des utilisateurs sur Dark/System/HC.
Taux FOUC : proportion avec « sursaut blanc » visible au départ (<1 %).
Contrast defects : nombre de bugs de contraste par version.
Error visibility : clics/répétitions en raison d'erreurs « non visibles » dans différents thèmes.
Impact énergétique (mobile) : comparaison des temps de session en darka vs aboyez (métrique indirecte).
13) Anti-modèles
Inverser tout « filter : invert (1) » - brise la marque et les sens.
Changer les couleurs directement dans les composants sans tokens.
Cacher les anneaux de focus dans un thème sombre.
Texte trop sombre sur fond sombre (ou clair sur clair).
Longue transition sur toute la page (sous-tordus).
Couleurs « exclusives » des statuts dans un thème qui n'existe pas dans l'autre.
14) Liste de vérification QA
Contraste et lisibilité
- Tous les textes du ≥ AA ; étiquettes critiques/petit texte ≥ AAA.
- Les erreurs/succès/avertissements ne sont pas seulement de couleur.
Comportement
- Le système respecte les 'prefers-color-scheme' et répond au changement d'OS.
- Pas de FOUC (sujet appliqué avant le rendu).
- Le basculement d'un thème ne réinitialise pas l'état des pages.
Composants
- Cartes/formulaires/tables n'utilisent que des jetons.
- Les graphiques ont des palettes pour les deux thèmes.
- Les logos/icônes sont correctement visibles dans les deux thèmes.
A11y
- Focus-ring visible ; Le mode High-Contrast est disponible.
- Est pris en compte « prefers-reduced-motion ».
Performance
- Transition ≤ 200 ms ; sans reflow global.
- Pas de filtres/mixtes lourds sur les conteneurs.
15) Documentation dans le système de conception
Theme tokens : palettes, contrastes, état (hover/focus/active/disabled).
Guides : comment ajouter un nouveau brendo-accent sans régression de contraste.
Charts/Media : palettes précontraintes pour light/dark.
Patterns : System-first, High-Contrast, commutation en douceur sans FOUC.
Do/Don't : inversion par filtre, inline-couleurs, erreurs invisibles/focus.
Résumé succinct
Le commutateur de thème de travail est un token sémantique + System-first + un démarrage incommensurable. Fixez le contraste, centralisez les couleurs, respectez 'prefers-color-scheme' et reduce-motion, conservez le choix de l'utilisateur et évitez les effets lourds. L'UI reste alors lisible et reconnaissable dans n'importe quel environnement - du match de vie nocturne à la caisse de jour et aux écrans de tournoi.