← Retour au blog
CSS variablesdark modethemingprefers-color-schemeAngularIonicperformance

Thématisation dynamique en CSS moderne : au-delà du dark mode basique

La thématisation dynamique n'est plus un luxe cosmétique : c'est devenu une attente utilisateur. Pourtant, beaucoup de développeurs se contentent encore d'une bascule dark/light rudimentaire avec du JavaScript qui force un repaint du DOM. CSS moderne nous offre bien mieux. Les variables CSS (custom properties), couplées à prefers-color-scheme et aux Media Queries intelligentes, permettent une thématisation fluide, performante et respectueuse des préférences système. L'enjeu réel n'est pas de changer de couleurs, mais de construire un système de tokens sémantiques qui s'adapte sans friction.

Variables CSS : fondation d'une thématisation solide

Les variables CSS (propriétés personnalisées) sont le socle de toute thématisation moderne. Contrairement aux variables SCSS ou LESS qui se résolvent à la compilation, les variables CSS existent à l'exécution et peuvent être modifiées dynamiquement. Cela signifie que vous pouvez changer une couleur sans rechargement, sans JavaScript compliqué. Déclarez vos tokens dans une pseudo-classe :root, puis héritez-les dans vos composants. Pour un système vraiment scalable, organisez vos tokens par catégorie : couleurs sémantiques (primary, success, error), couleurs neutres (gray-50 à gray-900), espacements, typographies. Cette organisation rend la maintenance triviale et permet aux designers de modifier le thème globalement. Un token bien nommé comme --color-surface-primary dit exactement ce qu'il fait ; --bg1 non.

:root {
  --color-text-primary: #1a1a1a;
  --color-text-secondary: #666666;
  --color-surface-primary: #ffffff;
  --color-surface-secondary: #f5f5f5;
  --color-border: #e0e0e0;
  --color-accent: #0066cc;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --transition-base: 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

body {
  color: var(--color-text-primary);
  background-color: var(--color-surface-primary);
  transition: background-color var(--transition-base),
              color var(--transition-base);
}

.card {
  background: var(--color-surface-secondary);
  border: 1px solid var(--color-border);
  padding: var(--spacing-md);
}```

Remarquez la transition fluide sur background-color et color : c'est crucial pour l'UX. Sans transition, le changement de thème est brusque et inconfortable. Avec une durée bien calibrée (100–200ms), l'utilisateur perçoit une transformation naturelle.

prefers-color-scheme : respect des préférences système

La media query prefers-color-scheme détecte si l'utilisateur a configuré un mode sombre ou clair au niveau système (macOS, Windows, iOS, Android). C'est une vraie préférence, pas un gadget. Utilisez-la pour initialiser votre thème correctement. La plupart des utilisateurs ne changeront jamais manuellement le thème s'il respecte leur système ; ceux qui le font méritent une option, mais le défaut doit être intelligent.

@media (prefers-color-scheme: dark) {
  :root {
    --color-text-primary: #f5f5f5;
    --color-text-secondary: #b0b0b0;
    --color-surface-primary: #1a1a1a;
    --color-surface-secondary: #2a2a2a;
    --color-border: #3a3a3a;
    --color-accent: #4da6ff;
  }
}```

Cette approche est élégante : une seule media query redéfinit tous les tokens. Pas de sélecteurs spécifiques, pas de cascade complexe. Le navigateur applique ces variables automatiquement sans JavaScript. Pour Android/Capacitor, vérifiez que votre statusbar et navigationbar adoptent le thème via le fichier de configuration Capacitor (statusBarBackgroundColor, navigationBarBackgroundColor).

Thème utilisateur : override intelligent avec data-attributes

Permettre à l'utilisateur de choisir manuellement un thème est courant. Utilisez un data-attribute (par exemple data-theme) plutôt que des classes, pour des raisons de performance et de maintenabilité. Les classes peuvent être overridées accidentellement par du CSS de librairie ; un data-attribute est explicite et prévisible.

[data-theme="dark"] {
  --color-text-primary: #f5f5f5;
  --color-text-secondary: #b0b0b0;
  --color-surface-primary: #1a1a1a;
  --color-surface-secondary: #2a2a2a;
  --color-border: #3a3a3a;
}

[data-theme="light"] {
  --color-text-primary: #1a1a1a;
  --color-text-secondary: #666666;
  --color-surface-primary: #ffffff;
  --color-surface-secondary: #f5f5f5;
  --color-border: #e0e0e0;
}

/* Préférence utilisateur > préférence système */
body {
  color-scheme: light dark;
}```

En JavaScript (ou Angular), le toggle est trivial : document.documentElement.setAttribute('data-theme', 'dark'). Stockez le choix dans localStorage pour la persistance. Cruciale : appliquez le data-theme dès le chargement initial (dans un script inline, avant le premier repaint) pour éviter le flash blanc en dark mode.

Pièges courants et anti-patterns

Un piège fréquent : utiliser des variables CSS partout, y compris pour des valeurs qui ne changent jamais (comme les rayons de bordure fixes). Cela pollue vos custom properties et complique la lecture. Réservez les variables CSS aux valeurs qui varient réellement selon le thème ou le contexte. Un autre piège : oublier que les variables CSS héritent. Si vous déclarez --color-text-primary sur un élément, tous ses enfants l'héritent. C'est puissant, mais cela peut créer des bugs subtils si vous redéclarez une variable à différents niveaux sans intention claire. Troisième piège : ne pas tester les contrastes WCAG en mode sombre. Une couleur lisible en clair ne l'est pas forcément en sombre. Utilisez des outils comme WebAIM Color Contrast Checker pour valider. Enfin, évitez de mélanger des variables CSS avec du JavaScript qui modifie directement les styles inline (style.color = '...') : c'est une bataille de priorités CSS qui finira mal.

Intégration avec Angular et Ionic

En Angular, créez un service ThemeService qui gère l'état du thème et persiste en localStorage. Injectez ce service au démarrage (dans app.component) pour appliquer le thème avant le premier rendu. Utilisez un Observable pour que tout composant s'abonne aux changements. Avec Ionic et Capacitor, synchronisez le thème avec StatusBar.setBackgroundColor() et NavigationBar.setBackgroundColor() pour une cohérence visuelle complète. Sur mobile, c'est particulièrement important : un thème sombre avec une statusbar blanche casse l'immersion.

@Injectable({ providedIn: 'root' })
export class ThemeService {
  private themeSubject = new BehaviorSubject<'light' | 'dark'>('light');
  theme$ = this.themeSubject.asObservable();

  constructor() {
    const saved = localStorage.getItem('theme');
    const preferred = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    const initial = (saved as 'light' | 'dark') || preferred;
    this.setTheme(initial);
  }

  setTheme(theme: 'light' | 'dark') {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
    this.themeSubject.next(theme);
  }
}

Intégrez ce service dans app.component.ts, et exposez un toggle dans votre header ou settings. Les variables CSS feront le reste.

Transitions fluides et performance

Les transitions sur les propriétés de couleur sont peu coûteuses (compositing GPU), mais trop de transitions simultanées peuvent créer du jank sur mobile. Limitez-les aux propriétés visibles : background-color, color, border-color. Évitez de transitionner width, height ou transform ; ce sont des layouts shifts qui forcent un repaint. Utilisez will-change avec parcimonie : will-change: background-color sur des éléments fréquemment re-rendus (body, main containers) peut améliorer la performance, mais l'abus consomme de la mémoire.

Conclusion

Une thématisation moderne n'est pas un détail UX, c'est une architecture CSS. Variables CSS sémantiques, prefers-color-scheme, data-attributes et transitions fluides constituent un système robuste, maintenable et respectueux des préférences utilisateur. Vous n'avez besoin que d'un service léger pour gérer l'état et la persistance ; le CSS fait presque tout le travail. Testez les contrastes, évitez les pièges courants (variables partout, transitions sur layout), et intégrez correctement avec votre framework (Angular, Ionic). Une thématisation bien pensée se fait oublier : c'est le signe qu'elle est vraiment fluide.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️