Aller au contenu principal

[Medium] 🎨 Implémentation du changement de thème

Question d'entretien​

Q : Quand une page doit supporter 2 styles différents (par exemple thème clair/sombre), comment organisez-vous le CSS ?

C'est une question qui évalue la conception d'architecture CSS et l'expérience pratique, impliquant :

  1. Conception d'architecture CSS
  2. Stratégie de changement de thème
  3. Application d'outils modernes (Tailwind CSS, CSS Variables)
  4. Considérations de performance et maintenabilité

Vue d'ensemble des solutions​

SolutionCas d'usageAvantagesInconvénientsRecommandation
CSS VariablesProjets navigateurs modernesChangement dynamique, bonnes performancesPas de support IE5/5 Fortement recommandé
Quasar + Pinia + SCSSProjets Vue 3 + QuasarÉcosystème complet, gestion d'étatNécessite Quasar Framework5/5 Fortement recommandé
Tailwind CSSDéveloppement rapideRapide, haute cohérenceCourbe d'apprentissage, HTML verbeux5/5 Fortement recommandé
Changement de class CSSCompatibilité anciens navigateursBonne compatibilitéCSS volumineux4/5 Recommandé
CSS ModulesProjets React/VueIsolation de portéeNécessite un bundler4/5 Recommandé
Styled ComponentsProjets ReactCSS-in-JS, styles dynamiquesSurcoût runtime4/5 Recommandé
Variables SASS/LESSThème décidé à la compilationPuissantPas de changement dynamique3/5 À considérer
Fichiers CSS séparésThèmes très différentsSéparation claireCoût de chargement, code dupliqué2/5 Non recommandé

Solution 1 : CSS Variables​

Concept clé​

Utilisation des propriétés CSS personnalisées (CSS Custom Properties), en changeant la class de l'élément racine pour modifier les valeurs des variables.

Implémentation​

1. Définir les variables de thème​

/* styles/themes.css */

/* Thème clair (par défaut) */
:root {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--color-background: #ffffff;
--color-text: #1f2937;
--color-border: #e5e7eb;
--color-card: #f9fafb;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* Thème sombre */
[data-theme='dark'] {
--color-primary: #60a5fa;
--color-secondary: #a78bfa;
--color-background: #1f2937;
--color-text: #f9fafb;
--color-border: #374151;
--color-card: #111827;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}

/* Si un troisième thème existe (par ex. mode protection des yeux) */
[data-theme='sepia'] {
--color-primary: #92400e;
--color-secondary: #78350f;
--color-background: #fef3c7;
--color-text: #451a03;
--color-border: #fde68a;
--color-card: #fef9e7;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

2. Utiliser les variables​

/* components/Button.css */
.button {
background-color: var(--color-primary);
color: var(--color-text);
border: 1px solid var(--color-border);
box-shadow: var(--shadow);
transition: all 0.3s ease;
}

.card {
background-color: var(--color-card);
color: var(--color-text);
border: 1px solid var(--color-border);
}

body {
background-color: var(--color-background);
color: var(--color-text);
}

3. Changement de thème en JavaScript​

// utils/theme.js

// Obtenir le thème actuel
function getCurrentTheme() {
return localStorage.getItem('theme') || 'light';
}

// Définir le thème
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}

// Basculer le thème
function toggleTheme() {
const currentTheme = getCurrentTheme();
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
}

// Initialisation (lecture des préférences utilisateur depuis localStorage)
function initTheme() {
const savedTheme = getCurrentTheme();
setTheme(savedTheme);

// Écouter les changements de thème système
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
// Si l'utilisateur n'a pas défini de préférence, suivre le système
setTheme(e.matches ? 'dark' : 'light');
}
});
}

// Initialiser au chargement de la page
initTheme();

4. Exemple d'intégration Vue 3​

<template>
<div>
<button @click="toggleTheme" class="theme-toggle">
<span v-if="currentTheme === 'light'">🌙 Mode sombre</span>
<span v-else>☀️ Mode clair</span>
</button>
</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const currentTheme = ref('light');

function toggleTheme() {
const newTheme = currentTheme.value === 'light' ? 'dark' : 'light';
currentTheme.value = newTheme;
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}

onMounted(() => {
const savedTheme = localStorage.getItem('theme') || 'light';
currentTheme.value = savedTheme;
document.documentElement.setAttribute('data-theme', savedTheme);
});
</script>

Avantages​

  • Changement dynamique : Pas besoin de recharger les fichiers CSS
  • Bonnes performances : Support natif du navigateur, seules les valeurs des variables changent
  • Facile à maintenir : Gestion centralisée des thèmes, modifications aisées
  • Extensible : Ajout facile d'un troisième ou quatrième thème

Inconvénients​

  • Pas de support IE : Nécessite un polyfill ou une solution de repli
  • Intégration avec les préprocesseurs : Attention lors de l'utilisation mixte avec les variables SASS/LESS

Solution 2 : Tailwind CSS​

Concept clé​

Utilisation de la variante dark: de Tailwind CSS et de la configuration de thème personnalisée, combinées avec le changement de class pour implémenter le thème.

Implémentation​

1. Configurer Tailwind​

// tailwind.config.js
module.exports = {
darkMode: 'class', // Utiliser la stratégie class (et non media query)
theme: {
extend: {
colors: {
// Couleurs personnalisées (possibilité de définir plusieurs jeux de couleurs)
primary: {
light: '#3b82f6',
dark: '#60a5fa',
},
background: {
light: '#ffffff',
dark: '#1f2937',
},
text: {
light: '#1f2937',
dark: '#f9fafb',
},
},
},
},
plugins: [],
};

2. Utiliser les classes de thème Tailwind​

<template>
<!-- Méthode 1 : utiliser la variante dark: -->
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
<h1 class="text-blue-600 dark:text-blue-400">Titre</h1>

<button
class="bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white px-4 py-2 rounded"
>
Bouton
</button>

<div
class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg shadow-md dark:shadow-lg"
>
<p class="text-gray-700 dark:text-gray-300">Contenu textuel</p>
</div>
</div>

<!-- Bouton de changement de thème -->
<button @click="toggleTheme" class="fixed top-4 right-4">
<svg v-if="isDark" class="w-6 h-6">
<!-- Icône soleil -->
</svg>
<svg v-else class="w-6 h-6">
<!-- Icône lune -->
</svg>
</button>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const isDark = ref(false);

function toggleTheme() {
isDark.value = !isDark.value;
updateTheme();
}

function updateTheme() {
if (isDark.value) {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
}
}

onMounted(() => {
// Lire la préférence de thème enregistrée
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

isDark.value = savedTheme === 'dark' || (!savedTheme && prefersDark);
updateTheme();
});
</script>

3. Avancé : Multi-thèmes personnalisés (plus de 2)​

// tailwind.config.js
module.exports = {
darkMode: 'class',
theme: {
extend: {
colors: {
theme: {
bg: 'var(--theme-bg)',
text: 'var(--theme-text)',
primary: 'var(--theme-primary)',
},
},
},
},
};
/* styles/themes.css */
:root {
--theme-bg: #ffffff;
--theme-text: #000000;
--theme-primary: #3b82f6;
}

[data-theme='dark'] {
--theme-bg: #1f2937;
--theme-text: #f9fafb;
--theme-primary: #60a5fa;
}

[data-theme='sepia'] {
--theme-bg: #fef3c7;
--theme-text: #451a03;
--theme-primary: #92400e;
}
<template>
<!-- Utiliser les variables de thème personnalisées -->
<div class="bg-theme-bg text-theme-text">
<button class="bg-theme-primary">Bouton</button>
</div>

<!-- Sélecteur de thème -->
<select @change="setTheme($event.target.value)">
<option value="light">Clair</option>
<option value="dark">Sombre</option>
<option value="sepia">Protection des yeux</option>
</select>
</template>

<script setup>
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}
</script>

Avantages de Tailwind​

  • Développement rapide : utility-first, pas besoin d'écrire du CSS
  • Cohérence : système de design intégré, maintien d'un style uniforme
  • tree-shaking : suppression automatique des styles inutilisés
  • RWD friendly : variantes responsive sm:, md:, lg:
  • Variantes de thème : dark:, hover:, focus: et autres variantes riches

Inconvénients​

  • HTML verbeux : beaucoup de classes, peut affecter la lisibilité
  • Courbe d'apprentissage : nécessite de maîtriser la nomenclature des utility classes
  • Personnalisation : la personnalisation poussée nécessite de comprendre la configuration

Solution 3 : Quasar + Pinia + SCSS (expérience récente)​

Expérience de projet réel : C'est la solution que j'ai utilisée dans un projet réel, intégrant Quasar Framework, la gestion d'état Pinia et le système de variables SCSS.

Concept clé​

Architecture multi-couches :

  1. Quasar Dark Mode API - Support de thème au niveau du framework
  2. Pinia Store - Gestion centralisée de l'état du thème
  3. SessionStorage - Persistance des préférences utilisateur
  4. SCSS Variables + Mixin - Variables de thème et gestion des styles

Flux d'architecture​

L'utilisateur clique sur le bouton de changement
↓
Quasar $q.dark.toggle()
↓
Pinia Store met à jour l'état
↓
Synchronisation avec SessionStorage
↓
Changement de class sur Body (.body--light / .body--dark)
↓
Mise à jour des variables CSS
↓
L'interface se met à jour automatiquement

Implémentation​

1. Pinia Store (gestion d'état)​

// src/stores/darkModeStore.ts
import { defineStore } from 'pinia';
import { useSessionStorage } from '@vueuse/core';

export const useDarkModeStore = defineStore('darkMode', () => {
// Utiliser SessionStorage pour persister l'état
const isDarkMode = useSessionStorage<boolean>('isDarkMode', false);

// Mettre à jour l'état Dark Mode
const updateIsDarkMode = (status: boolean) => {
isDarkMode.value = status;
};

return {
isDarkMode,
updateIsDarkMode,
};
});

2. Configuration Quasar​

// quasar.config.js
module.exports = configure(function (/* ctx */) {
return {
framework: {
config: {
dark: 'true', // Activer le support Dark Mode
},
plugins: ['Notify', 'Loading', 'Dialog'],
},
};
});

3. Système de variables de thème SCSS​

// assets/css/_variable.scss

// Définition du mappage de variables pour les thèmes Light et Dark
$themes: (
light: (
--bg-main: #ffffff,
--bg-side: #f0f1f4,
--text-primary: #000000,
--text-secondary: #666666,
--primary-color: #2d7eff,
--border-color: #e5ebf2,
),
dark: (
--bg-main: #081f2d,
--bg-side: #0d2533,
--text-primary: #ffffff,
--text-secondary: #b0b0b0,
--primary-color: #2d7eff,
--border-color: #14384d,
),
);

// Mixin : Appliquer les variables CSS selon le thème
@mixin theme-vars($theme) {
@each $key, $value in map-get($themes, $theme) {
#{$key}: #{$value};
}
}

// Mixin : Styles spécifiques au Light Mode
@mixin light {
.body--light & {
@content;
}
}

// Mixin : Styles spécifiques au Dark Mode
@mixin dark {
.body--dark & {
@content;
}
}

4. Application globale du thème​

// src/css/app.scss
@import 'assets/css/_variable.scss';

// Appliquer le Light Theme par défaut
:root {
@include theme-vars('light');
}

// Appliquer le Dark Theme en Dark Mode
.body--dark {
@include theme-vars('dark');
}

5. Utilisation dans les composants​

Méthode A : Utiliser les CSS Variables (recommandée)

<template>
<div class="my-card">
<h2 class="title">Titre</h2>
<p class="content">Contenu textuel</p>
</div>
</template>

<style scoped lang="scss">
.my-card {
background: var(--bg-main);
color: var(--text-primary);
border: 1px solid var(--border-color);
padding: 1rem;
}

.title {
color: var(--primary-color);
font-size: 1.5rem;
}

.content {
color: var(--text-secondary);
}
</style>

Méthode B : Utiliser les SCSS Mixins (avancée)

<template>
<button class="custom-btn">Bouton</button>
</template>

<style scoped lang="scss">
@import 'assets/css/_variable.scss';

.custom-btn {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
transition: all 0.3s ease;

@include light {
background: #2d7eff;
color: #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);

&:hover {
background: #1a5fd9;
}
}

@include dark {
background: #1677ff;
color: #ffffff;
box-shadow: 0 2px 4px rgba(255, 255, 255, 0.1);

&:hover {
background: #0d5acc;
}
}
}
</style>

6. Fonctionnalité de changement​

<template>
<button @click="toggleDarkMode" class="theme-toggle">
<q-icon :name="isDarkMode ? 'light_mode' : 'dark_mode'" />
{{ isDarkMode ? 'Passer au mode clair' : 'Passer au mode sombre' }}
</button>
</template>

<script setup lang="ts">
import { useQuasar } from 'quasar';
import { onMounted } from 'vue';
import { useDarkModeStore } from 'stores/darkModeStore';

const $q = useQuasar();
const { isDarkMode, updateIsDarkMode } = useDarkModeStore();

// Changer de thème
const toggleDarkMode = () => {
$q.dark.toggle(); // Changement Quasar
updateIsDarkMode($q.dark.isActive); // Synchroniser avec le Store
};

// Restaurer les préférences utilisateur au chargement de la page
onMounted(() => {
if (isDarkMode.value) {
$q.dark.set(true);
}
});
</script>

<style scoped lang="scss">
.theme-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--primary-color);
color: var(--text-primary);
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: opacity 0.3s ease;

&:hover {
opacity: 0.8;
}
}
</style>

Avantages​

  • Écosystème complet : Solution tout-en-un Quasar + Pinia + VueUse
  • Gestion d'état : Pinia centralise la gestion, facile à tester et maintenir
  • Persistance : SessionStorage sauvegarde automatiquement, pas de perte au rafraîchissement
  • Sûreté des types : Support TypeScript, réduction des erreurs
  • Expérience de développement : Les SCSS Mixins simplifient le développement de styles
  • Bonnes performances : CSS Variables mise à jour dynamique, pas besoin de rechargement

Inconvénients​

  • Dépendance au framework : Nécessite Quasar Framework
  • Coût d'apprentissage : Nécessite de maîtriser Quasar, Pinia, SCSS
  • Volume plus important : Le framework complet est plus lourd que du CSS simple

Bonnes pratiques​

// composables/useTheme.ts
import { computed } from 'vue';
import { useQuasar } from 'quasar';
import { useDarkModeStore } from 'stores/darkModeStore';

export function useTheme() {
const $q = useQuasar();
const store = useDarkModeStore();

const isDark = computed(() => store.isDarkMode);

const toggleTheme = () => {
$q.dark.toggle();
store.updateIsDarkMode($q.dark.isActive);
};

const setTheme = (dark: boolean) => {
$q.dark.set(dark);
store.updateIsDarkMode(dark);
};

return {
isDark,
toggleTheme,
setTheme,
};
}

Comment le présenter en entretien​

"Dans mon dernier projet, nous avons implémenté un système complet de Dark Mode avec Quasar + Pinia + SCSS :

  1. Gestion d'état : Gestion unifiée de l'état du thème via Pinia Store, avec persistance via useSessionStorage de VueUse
  2. Système de styles : Définition des variables de thème avec SCSS Map + Mixin, appliquées dans :root et .body--dark
  3. Mécanisme de changement : Contrôle via l'API $q.dark de Quasar, ajout automatique de la class correspondante sur <body>
  4. Expérience de développement : Fourniture des mixins @include light et @include dark pour un développement de styles plus intuitif

Cette solution a bien fonctionné dans notre projet : changement fluide, état stable, facile à maintenir."


Solution 4 : Changement de class CSS​

Implémentation​

/* styles/themes.css */

/* Thème clair */
body.theme-light {
background-color: #ffffff;
color: #000000;
}

body.theme-light .button {
background-color: #3b82f6;
color: #ffffff;
}

body.theme-light .card {
background-color: #f9fafb;
border: 1px solid #e5e7eb;
}

/* Thème sombre */
body.theme-dark {
background-color: #1f2937;
color: #f9fafb;
}

body.theme-dark .button {
background-color: #60a5fa;
color: #000000;
}

body.theme-dark .card {
background-color: #111827;
border: 1px solid #374151;
}
// Changer de thème
function setTheme(theme) {
document.body.className = `theme-${theme}`;
localStorage.setItem('theme', theme);
}

Cas d'utilisation​

  • Besoin de supporter IE et les anciens navigateurs
  • Différences de thème trop importantes pour utiliser des variables
  • Ne pas vouloir introduire de dépendances supplémentaires

Solution 5 : Fichiers CSS séparés (non recommandé)​

Implémentation​

<!-- Chargement dynamique du CSS -->
<link id="theme-stylesheet" rel="stylesheet" href="/styles/theme-light.css" />
function setTheme(theme) {
const link = document.getElementById('theme-stylesheet');
link.href = `/styles/theme-${theme}.css`;
}

Inconvénients​

  • Coût de chargement : Le CSS doit être retéléchargé lors du changement
  • FOUC : Possibilité de flash de contenu sans style
  • Code dupliqué : Les styles communs doivent être définis en double

Intégration du design responsive (RWD)​

Tailwind CSS + RWD + Changement de thème​

<template>
<div
class="
/* Styles de base */
p-4 rounded-lg transition-colors

/* Thème clair */
bg-white text-gray-900

/* Thème sombre */
dark:bg-gray-800 dark:text-gray-100

/* RWD: Mobile */
text-sm

/* RWD: Tablette et plus */
md:text-base md:p-6

/* RWD: Desktop et plus */
lg:text-lg lg:p-8

/* États d'interaction */
hover:shadow-lg hover:scale-105
"
>
<h2
class="
font-bold
text-xl md:text-2xl lg:text-3xl
text-blue-600 dark:text-blue-400
"
>
Titre responsive
</h2>

<p class="mt-2 text-gray-700 dark:text-gray-300">Contenu textuel</p>

<!-- Grille responsive -->
<div
class="
grid
grid-cols-1 /* Mobile: 1 colonne */
sm:grid-cols-2 /* Petite tablette: 2 colonnes */
md:grid-cols-3 /* Tablette: 3 colonnes */
lg:grid-cols-4 /* Desktop: 4 colonnes */
gap-4
"
>
<div
v-for="item in items"
:key="item.id"
class="
p-4 rounded
bg-gray-100 dark:bg-gray-700
hover:bg-gray-200 dark:hover:bg-gray-600
"
>
{{ item.name }}
</div>
</div>
</div>
</template>

CSS Variables + Media Queries​

/* Variables de base */
:root {
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--font-size-base: 16px;
}

/* Ajustement de l'espacement pour tablette et plus */
@media (min-width: 768px) {
:root {
--spacing-sm: 0.75rem;
--spacing-md: 1.5rem;
--spacing-lg: 2rem;
}
}

/* Ajustement de la police pour desktop et plus */
@media (min-width: 1024px) {
:root {
--font-size-base: 18px;
}
}

/* Utilisation des variables */
.container {
padding: var(--spacing-md);
font-size: var(--font-size-base);
}

/* Thème sombre + RWD */
@media (min-width: 768px) {
[data-theme='dark'] {
--shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
}
}

Conseils d'optimisation des performances​

1. Éviter le FOUC (Flash of Unstyled Content)​

<!-- Exécuter immédiatement dans le <head>, éviter le flash -->
<script>
(function () {
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', theme);
if (theme === 'dark') {
document.documentElement.classList.add('dark');
}
})();
</script>

2. Utiliser prefers-color-scheme​

/* Détection automatique du thème système */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
/* Si l'utilisateur n'a pas défini de préférence, suivre le système */
--color-background: #1f2937;
--color-text: #f9fafb;
}
}
// Détection JavaScript
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (!localStorage.getItem('theme')) {
setTheme(prefersDark ? 'dark' : 'light');
}

3. Transitions CSS animées​

/* Transition fluide */
* {
transition: background-color 0.3s ease, color 0.3s ease,
border-color 0.3s ease;
}

/* Ou pour des éléments spécifiques */
body,
.card,
.button {
transition: all 0.3s ease;
}

4. Réduire le Reflow​

/* Utiliser transform plutôt que de modifier directement la largeur/hauteur */
.theme-switching {
transform: scale(1);
transition: transform 0.3s ease;
}

.theme-switching:hover {
transform: scale(1.05); /* Accélération GPU */
}

Architecture de projet réel​

Structure de fichiers​

src/
├── styles/
│ ├── themes/
│ │ ├── variables.css # Définition des CSS Variables
│ │ ├── light.css # Thème clair
│ │ ├── dark.css # Thème sombre
│ │ └── sepia.css # Thème protection des yeux
│ ├── base.css # Styles de base
│ └── components/ # Styles des composants
│ ├── button.css
│ └── card.css
├── utils/
│ └── theme.js # Logique de changement de thème
└── components/
└── ThemeToggle.vue # Composant de changement de thème

Bonnes pratiques​

// composables/useTheme.js (Vue 3 Composition API)
import { ref, onMounted, watch } from 'vue';

export function useTheme() {
const theme = ref('light');
const themes = ['light', 'dark', 'sepia'];

function setTheme(newTheme) {
if (!themes.includes(newTheme)) return;

theme.value = newTheme;
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);

// Tailwind dark mode
if (newTheme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}

function toggleTheme() {
const currentIndex = themes.indexOf(theme.value);
const nextIndex = (currentIndex + 1) % themes.length;
setTheme(themes[nextIndex]);
}

function initTheme() {
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia(
'(prefers-color-scheme: dark)'
).matches;

if (savedTheme) {
setTheme(savedTheme);
} else if (prefersDark) {
setTheme('dark');
}
}

onMounted(() => {
initTheme();

// Écouter les changements de thème système
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
});
});

return {
theme,
themes,
setTheme,
toggleTheme,
};
}

Modèle de réponse d'entretien​

Interviewer : Quand une page doit supporter 2 styles différents, comment organisez-vous le CSS ?

Réponse type A : Montrer l'expérience réelle (recommandé)

"Je choisis la solution la plus adaptée en fonction de la stack technique du projet. Dans mon dernier projet, nous avons utilisé Quasar + Pinia + SCSS :

1. Gestion d'état (30 sec)

  • Gestion unifiée de l'état du thème via Pinia Store
  • Persistance avec useSessionStorage de VueUse
  • Contrôle du thème via l'API $q.dark de Quasar

2. Système de styles (1 min)

// Définition des variables de thème avec SCSS Map
$themes: (
light: (
--bg-main: #fff,
--text: #000,
),
dark: (
--bg-main: #081f2d,
--text: #fff,
),
);

// Application dans :root et .body--dark
:root {
@include theme-vars('light');
}
.body--dark {
@include theme-vars('dark');
}
  • Les composants utilisent var(--bg-main) pour le changement automatique
  • Fourniture des mixins @include light / @include dark pour les styles complexes

3. Mécanisme de changement (30 sec)

const toggleTheme = () => {
$q.dark.toggle(); // Changement Quasar
store.updateIsDarkMode($q.dark.isActive); // Synchronisation Store
};

4. Résultats concrets (30 sec)

  • Changement fluide sans flash (mise à jour dynamique CSS Variables)
  • État persistant (le thème est conservé au rafraîchissement)
  • Facile à maintenir (variables de thème centralisées)
  • Haute productivité (les Mixins simplifient le développement de styles)"

Réponse type B : Solution universelle (alternative)

"Pour les projets modernes, je recommande CSS Variables + Tailwind CSS :

1. Architecture (30 sec)

  • Définition des variables de thème avec CSS Variables (couleurs, espacement, ombres, etc.)
  • Changement de thème via l'attribut data-theme sur l'élément racine
  • Développement rapide avec la variante dark: de Tailwind

2. Points clés d'implémentation (1 min)

:root {
--color-bg: #fff;
--color-text: #000;
}
[data-theme='dark'] {
--color-bg: #1f2937;
--color-text: #f9fafb;
}

En JavaScript, il suffit de changer l'attribut data-theme pour que le navigateur applique automatiquement les variables correspondantes.

3. Intégration RWD (30 sec)

<div class="text-sm md:text-base lg:text-lg dark:bg-gray-800"></div>

Permet de gérer simultanément le RWD et le changement de thème.

4. Bonnes pratiques (30 sec)

  • Initialisation immédiate du thème dans le <head> pour éviter le FOUC
  • Utilisation de localStorage pour sauvegarder les préférences utilisateur
  • Détection de prefers-color-scheme pour suivre le thème système"

Questions de suivi​

Q1 : Comment supporter IE ?

R : Utiliser la solution de changement de class CSS, ou utiliser le polyfill css-vars-ponyfill.

Q2 : Comment éviter le flash lors du changement de thème ?

R : Exécuter immédiatement un script dans le <head> HTML pour définir le thème avant le rendu de la page.

Q3 : Comment gérer plusieurs thèmes ?

R : Il est recommandé d'utiliser un système de Design Tokens pour gérer uniformément toutes les variables de thème, avec synchronisation via Figma Variables.

Q4 : Comment tester les différents thèmes ?

R : Utiliser Storybook avec storybook-addon-themes pour tester visuellement toutes les variantes de thème.


Sujets connexes​

Reference​