Mode sombre
Comment le système de jetons s'adapte au mode sombre.
Thématisation de première classe avec parité clair/sombre
Chaque composant ePassLive est livré par défaut avec une variante claire et une variante sombre. Les thèmes ne sont pas une couche ajoutée après coup — les deux palettes sont conçues, vérifiées et testées comme partie intégrante du système.
Bascule de thème
Basculez entre le mode clair et le mode sombre. Votre préférence est sauvegardée sous la clé applicative epasslive-theme ; docs-theme est conservée uniquement comme repli legacy.
Correspondance des jetons
Les 70 jetons de couleur ci-dessous proviennent exclusivement de la vérité générée (_dark-mode.generated.ts), reconstruite depuis globals.css par pnpm --filter storefront docs:dark. Les échantillons affichent côte à côte les valeurs générées littérales ; le badge stable marque les jetons qui gardent la même valeur dans les deux thèmes.
Marque
Texte
Surfaces
Bordures
Sémantique
Teintes de statut
Arrière-plans de statut
Bouton principal
Neutre
Séries de graphiques
Code-barres
Superposition et voiles
Anneau de focus
Comportements au-delà des échanges de couleurs
Certaines adaptations du mode sombre vont au-delà du simple échange de valeur hexadécimale — elles changent la façon dont une valeur s’affiche ou se résout. Elles sont documentées ici pour ne pas être prises pour des bogues :
- Halo d’ombre (EX-QA-PASS-7a) : les cinq jetons d’ombre gagnent un halo blanc en inset en mode sombre afin que l’élévation reste visible sur les surfaces #1A1A1A.
- Bascule de l’anneau de focus : --ep-focus-ring fait passer son anneau intérieur de #FFFFFF à #1A1A1A en mode sombre, tandis que l’anneau extérieur sarcelle de 4 px reste stable.
- Inversion complète du code-barres : --ep-barcode-fg et --ep-barcode-bg échangent premier plan et arrière-plan en mode sombre afin que les lecteurs lisent toujours des barres sombres sur un champ clair ; la prop tone peut outrepasser ce comportement pour les badges imprimés.
- Échange du lien de texte : --ep-text-link passe de #0066FF à #009FCA en mode sombre, car le bleu sur #1A1A1A n’atteint que 3,60:1 — sous le seuil AA — alors que la sarcelle passe à 5,65:1.
- Indirection d’alias sémantique : --ep-semantic-error porte une définition identique dans les deux blocs de thème, mais se résout différemment à travers les chaînes var() — c’est l’aliasing, et non une valeur dupliquée, qui porte le comportement sombre.
Jetons non couleur stables entre thèmes
Les 60 jetons restants (rayons, espacement, polices, tailles de police, transitions, z-index, mouvement, zone tactile) ne sont définis que dans :root — stables entre thèmes par héritage, et non par duplication.
Vérification des contrastes WCAG 2.2
Paires de texte en mode sombre recalculées au rendu à partir des valeurs générées (les paires du thème clair se trouvent sur la page Couleurs).
Toutes les paires respectent WCAG 2.2 AA (≥4,5:1 pour le texte normal). Exactement deux paires se situent à moins de 0,33 du seuil AA : --ep-text-muted sur #1E293B (4,76:1, soit 0,26 au-dessus) et --ep-text-teal sur #1E293B (4,75:1, soit 0,25 au-dessus). Les deux ont été resserrées en v3.1.2 (DS-CORR-00-03) pour franchir AA — surveillez-les lors des régressions ; aucune dérive de contraste supplémentaire n’est permise.
Implémentation
Le mode sombre repose sur des propriétés CSS personnalisées. L’attribut data-theme="dark" sur l’élément html active les valeurs sombres des jetons.
Comment le thème sombre est déclenché
- Le déclencheur à l’exécution est l’attribut data-theme sur document.documentElement — pas une bascule de classe. La classe .dark n’est jamais définie dans la coque applicative réelle (voir theme-init-script.tsx).
- Le script d’initialisation du thème s’exécute avant l’hydratation de React afin d’éviter tout flash de contenu clair (FOUC).
- La préférence de l’utilisateur persiste dans localStorage sous la clé epasslive-theme ; le thème résolu pilote chaque jeton --ep-* à travers la cascade [data-theme="dark"].
- Les couleurs de marque (sarcelle, bleu, cyan, ambre, indigo, rose) et les séries de graphiques sont stables entre thèmes : même hexadécimal dans les deux modes, par conception.
Liste de vérification d’implantation
- Appliquez data-theme="dark" le plus tôt possible pour éviter un flash de contenu clair (FOUC).
- Utilisez les variantes Tailwind dark: qui pointent vers les mêmes jetons sémantiques.
- Auditez les ombres séparément — le même jeton --ep-shadow-md se résout en une opacité plus élevée en mode sombre.
- Vérifiez que les anneaux de focus restent visibles sur les surfaces sombres.
À éviter
- Les couleurs codées en dur. Utilisez toujours un jeton sémantique.
- Les branchements sur prefers-color-scheme à l’intérieur d’un composant. Effectuez le branchement sur le jeton, pas sur le thème.
- L’inversion automatique des gris. La palette sombre a été ajustée à la main pour conserver un contraste supérieur à WCAG AA.
- Oublier un état. Testez survol, focus-visible, désactivé et erreur en mode sombre.
Réalité architecturale
- Coque marketing de la boutique : adaptative au thème, pas sombre uniquement — chaque surface utilise des jetons sémantiques (bg-ep-bg, text-ep-text-heading…) qui suivent l’attribut data-theme de l’élément html. La bascule se trouve dans SiteHeaderV2 et le ThemeProvider applicatif est sombre par défaut ; le chrome (en-tête, tiroir, pied de page) suit le thème global sur toutes les routes selon l’ADR-062 amendé (2026-11-04).
- Tableau de bord admin (packages/storefront/src/components/dashboard/) : partage le ThemeProvider applicatif ; ThemeContext le republie vers ThemeToggle et DashboardShell reflète data-theme sur l’enveloppe .ep-admin.
- Coque de la documentation (DocsThemeToggle.tsx) : écrit data-theme sur l’élément html globalement — il n’existe pas d’enveloppe dédiée .ep-docs. Le shell de documentation lit epasslive-theme d’abord et utilise docs-theme uniquement comme repli legacy ; la bascule écrit les deux clés.
- Portail client (packages/client-portal) : livré avec un PortalThemeToggle indépendant qui persiste sous sa propre clé epasslive-portal-theme.
Thématisation locale
Un sous-arbre peut passer en sombre pendant que la page reste en clair. L’attribut data-theme sur n’importe quel élément ré-enracine la cascade de jetons pour tout son sous-arbre (ADR-062).
- Aucune surface du storefront n'est épinglée en sombre aujourd'hui : la lane homepage v2 (accueil + pages marché) suit le thème global depuis le 2026-11-04 (ADR-062 amendement 2) — ses rôles de couleur sont déclarés light/dark dans home-v2.css — et la SearchSheet utilise themeScope="inherit" (ADR-063). Le mécanisme documenté ici reste disponible pour de futures bandes de marque.
- Le chrome suit le thème global : depuis l’amendement ADR-062 du 2026-11-04, l’en-tête, le tiroir mobile et le pied de page ont quitté la bande et suivent le thème global sur toutes les routes via la couche échangeuse --home-chrome-* de home-v2.css (docs/design/home-v2-deviations.md §1.1).
- Le contraste est vérifié par fond effectif (ADR-065) : le texte posé sur une surface à l’intérieur d’une racine scopée est mesuré contre cette racine, quel que soit le thème de la page.