Prérequis
- Niveau : bases CSS (variables) + JavaScript (cf. variables CSS).
- Outils : VS Code + Live Server, navigateur moderne.
- Temps estimé : 1 h 30.
Pourquoi un mode sombre ?
Le mode sombre est devenu une attente standard : il réduit la fatigue oculaire la nuit, économise jusqu’à 60 % de batterie sur OLED, et 80 % des utilisateurs le préfèrent en soirée. Bien implémenté, il prend 30 lignes de CSS : variables CSS + un attribut sur <html>.
Le mode sombre : pourquoi l’adopter ?
Le mode sombre (dark mode) réduit la fatigue oculaire, économise la batterie sur les écrans OLED, et la plupart des utilisateurs le préfèrent. Voici comment l’implémenter proprement avec CSS et JavaScript.
Étape 1 : Les variables CSS (la base)
Définissez vos couleurs comme variables CSS pour pouvoir les changer facilement :
:root {
/* Mode clair (par défaut) */
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #1a1a1a;
--text-secondary: #666666;
--accent: #667eea;
--border: #e0e0e0;
--card-bg: #ffffff;
--card-shadow: rgba(0, 0, 0, 0.1);
}
/* Mode sombre */
[data-theme="dark"] {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--accent: #818cf8;
--border: #2d3748;
--card-bg: #1e293b;
--card-shadow: rgba(0, 0, 0, 0.4);
}
Étape 2 : Appliquer les variables dans le CSS
Voici la mise en pratique pour Étape 2 : Appliquer les variables dans le CSS. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
body {
background-color: var(--bg-primary);
color: var(--text-primary);
transition: background-color 0.3s ease, color 0.3s ease;
}
.card {
background: var(--card-bg);
border: 1px solid var(--border);
box-shadow: 0 2px 10px var(--card-shadow);
border-radius: 12px;
padding: 20px;
transition: all 0.3s ease;
}
header {
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
}
a { color: var(--accent); }
h1, h2, h3 { color: var(--text-primary); }
p { color: var(--text-secondary); }
Étape 3 : Le bouton de bascule
Voici la mise en pratique pour Étape 3 : Le bouton de bascule. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
<!-- HTML du toggle -->
<button id="theme-toggle" aria-label="Changer de thème">
<span class="icon-soleil">☀️</span>
<span class="icon-lune">🌙</span>
</button>
<style>
#thème-toggle {
background: var(--bg-secondary);
border: 2px solid var(--border);
border-radius: 30px;
padding: 8px 16px;
cursor: pointer;
font-size: 20px;
transition: all 0.3s;
}
/* Cacher l'icône inactive */
[data-theme="dark"] .icon-soleil { display: inline; }
[data-theme="dark"] .icon-lune { display: none; }
:root .icon-soleil { display: none; }
:root .icon-lune { display: inline; }
</style>
Étape 4 : Le JavaScript (avec sauvegarde du choix)
Voici la mise en pratique pour Étape 4 : Le JavaScript (avec sauvegarde du choix). Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
const toggle = document.getElementById('theme-toggle');
// Vérifier la préférence sauvegardée ou système
function getThemePreference() {
const saved = localStorage.getItem('theme');
if (saved) return saved;
// Détecter la préférence du système
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark' : 'light';
}
// Appliquer le thème
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}
// Initialiser au chargement
setTheme(getThemePreference());
// Basculer au clic
toggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const newTheme = current === 'dark' ? 'light' : 'dark';
setTheme(newTheme);
});
// Écouter les changements de préférence système
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
});
💡 Astuce : éviter le flash blanc
Placez ce script dans le <head> (avant le CSS) pour appliquer le thème avant l’affichage :
<script>
const t = localStorage.getItem('theme') ||
(matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', t);
</script>
Gérer les images en mode sombre
Voici la mise en pratique pour Gérer les images en mode sombre. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
/* Réduire la luminosité des images en mode sombre */
[data-theme="dark"] img {
filter: brightness(0.85);
transition: filter 0.3s;
}
/* Image spécifique pour le mode sombre */
<picture>
<source srcset="logo-dark.png" media="(prefers-color-scheme: dark)">
<img src="logo-light.png" alt="Logo">
</picture>
/* Ou avec CSS */
.logo-light { display: block; }
.logo-dark { display: none; }
[data-theme="dark"] .logo-light { display: none; }
[data-theme="dark"] .logo-dark { display: block; }
Toggle animé (switch iOS style)
Voici la mise en pratique pour Toggle animé (switch iOS style). Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
<label class="switch">
<input type="checkbox" id="dark-toggle">
<span class="slider"></span>
</label>
<style>
.switch {
position: relative;
width: 56px;
height: 28px;
}
.switch input { display: none; }
.slider {
position: absolute;
inset: 0;
background: #ccc;
border-radius: 28px;
cursor: pointer;
transition: 0.3s;
}
.slider::before {
content: "☀️";
position: absolute;
height: 22px;
width: 22px;
left: 3px;
bottom: 3px;
background: white;
border-radius: 50%;
transition: 0.3s;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
input:checked + .slider { background: #667eea; }
input:checked + .slider::before {
transform: translateX(28px);
content: "🌙";
}
</style>
Erreurs fréquentes
Flash blanc au chargement (FOUC)
Cause : le JavaScript qui applique le thème s’exécute après le rendu CSS.
Solution : placez un petit script synchrone dans le <head> avant le CSS, qui lit localStorage et applique l’attribut data-theme immédiatement.
Le thème change mais les images sont éblouissantes
Cause : les images blanches gardent leur fond clair en mode sombre.
Solution : baissez le filter: brightness(0.85), ou servez deux versions de l’image avec <picture> et media="(prefers-color-scheme: dark)".
Contraste insuffisant en mode sombre
Cause : texte gris foncé sur fond noir → ratio de contraste < 4.5:1, échec WCAG AA.
Solution : testez avec WebAIM Contrast Checker. Visez 7:1 (AAA) pour le texte courant.
Identifiants/attributs avec accents
Cause : data-thème, id="thème-toggle" — valides en HTML5 mais déconseillés (sélecteurs CSS plus complexes, parsers anciens).
Solution : utilisez exclusivement de l’ASCII pour les noms d’attributs et identifiants (data-theme, id="theme-toggle").
Exercice pratique
🎯 Défi : Mode sombre complet
- Créez une page avec header, cards et footer
- Définissez les variables CSS pour les 2 thèmes
- Ajoutez un toggle animé type switch
- Sauvegardez le choix dans
localStorage - Détectez la préférence système avec
prefers-color-scheme - Bonus : ajoutez un 3ème mode « auto » qui suit le système
Étape 3 : Détecter prefers-color-scheme
Voici la mise en pratique pour Étape 3 : Détecter prefers-color-scheme. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
:root { --fond: #ffffff; --texte: #1a1a1a; --lien: #0046ad; }
@media (prefers-color-scheme: dark) {
:root { --fond: #0f1419; --texte: #e6e6e6; --lien: #6ea8ff; }
}
body { background: var(--fond); color: var(--texte); }
Sur un Tecno Camon acheté à Abidjan avec Android 13, ce CSS bascule automatiquement le matin si l’utilisateur a programmé le mode sombre nocturne.
Étape 4 : Bascule manuelle persistante
Voici la mise en pratique pour Étape 4 : Bascule manuelle persistante. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
:root[data-theme='dark'] { --fond: #0f1419; --texte: #e6e6e6; --lien: #6ea8ff; }
const bouton = document.getElementById('toggle-theme');
const racine = document.documentElement;
function appliquerTheme(theme) {
racine.setAttribute('data-theme', theme);
bouton.setAttribute('aria-pressed', theme === 'dark');
}
bouton.addEventListener('click', () => {
const courant = racine.getAttribute('data-theme') || 'light';
const cible = courant === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', cible);
appliquerTheme(cible);
});
Étape 5 : Empêcher le flash blanc (FOUC)
Voici la mise en pratique pour Étape 5 : Empêcher le flash blanc (FOUC). Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
<script>
(function() {
var stocke = localStorage.getItem('theme');
var systeme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', stocke || systeme);
})();
</script>
Étape 6 : Contraste WCAG AA
| Fond | Texte | Ratio | Verdict |
|---|---|---|---|
| #0f1419 | #e6e6e6 | 13,4:1 | AAA |
| #1a1a1a | #9b9b9b | 4,8:1 | AA |
| #1a1a1a | #6e6e6e | 2,7:1 | Échec |
Étape 7 : Adoucir la transition
Voici la mise en pratique pour Étape 7 : Adoucir la transition. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
body { transition: background-color 200ms ease, color 200ms ease; }
@media (prefers-reduced-motion: reduce) { body { transition: none; } }
Sur les écrans OLED comme ceux du Samsung Galaxy A55, les pixels noirs sont éteints, ce qui économise environ 30 % de batterie.
Étape 8 : Synchroniser entre onglets
Voici la mise en pratique pour Étape 8 : Synchroniser entre onglets. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
window.addEventListener('storage', (e) => {
if (e.key === 'theme' && e.newValue) appliquerTheme(e.newValue);
});
Étape 9 : Adapter images et icônes
Voici la mise en pratique pour Étape 9 : Adapter images et icônes. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.
<svg width='24' height='24' viewBox='0 0 24 24'>
<path fill='currentColor' d='M12 2 L22 22 H2 Z'/>
</svg>
.bandeau { background-image: url('hero-clair.webp'); }
:root[data-theme='dark'] .bandeau { background-image: url('hero-sombre.webp'); }
Adaptation au contexte ouest-africain : performance, équipe, marché
Pour un développeur ou une PME basée à Dakar, Abidjan, Bamako, Cotonou, Lomé, Ouagadougou, Niamey ou Conakry qui livre des sites web ou applications custom, trois adaptations pèsent sur le succès des projets. Premièrement, la connectivité 4G inégale impose de réduire le poids des pages au strict nécessaire. Deuxièmement, le profil typique des développeurs disponibles localement est majoritairement formé sur du JavaScript moderne et du PHP, avec une expertise variable sur les outils plus avancés (TypeScript, frameworks edge, design systems). Troisièmement, le coût en FCFA des services cloud doit être anticipé : Hetzner CX22 à 4 500 FCFA/mois reste imbattable pour un démarrage, Cloudflare Pages gratuit pour les sites statiques, Backblaze B2 à 6 USD/TB/mois pour les sauvegardes. Pour les projets B2C qui exigent une latence faible, héberger sur un CDN avec PoP africain (Cloudflare Lagos, Africa Data Centres) divise par trois la latence perçue par rapport à un déploiement européen sans CDN.
Tester sur appareils réels avant la mise en production
Plus important que tous les outils synthétiques, tester son site sur un Android d’entrée de gamme avec une connexion 4G locale dégradée donne le seul verdict qui compte. Galaxy A03 à 200 EUR neuf, Tecno Spark, Itel A60 sont les appareils dominants chez les visiteurs ouest-africains. Sur ces téléphones, un site qui semble rapide sur DevTools peut être laggy en réalité. Pour approfondir sur les patterns frontend modernes, voir aussi le guide événements JavaScript.
Erreurs courantes à éviter en production
Trois patterns reviennent dans les projets web mal exécutés et coûtent cher à corriger plus tard. Premier pattern : copier-coller de code Stack Overflow sans comprendre le contexte d’origine. Une solution qui marche pour un cas particulier devient un bug subtil dans un autre. Deuxième pattern : ignorer les warnings de la console. Chaque warning est un signal qui mérite d’être lu et compris. Troisième pattern : ne pas tester sur de vrais appareils. DevTools simule mais ne remplace pas un test physique sur un Android d’entrée de gamme avec une 4G dégradée. Documenter chaque décision technique majeure dans un fichier ADR (Architecture Decision Record) prend dix minutes et fait gagner des heures lors d’un incident ou d’un audit.
Erreurs courantes à éviter en production
Trois patterns reviennent dans les projets web mal exécutés et coûtent cher à corriger plus tard. Premier pattern : copier-coller de code Stack Overflow sans comprendre le contexte d’origine. Une solution qui marche pour un cas particulier devient un bug subtil dans un autre. Deuxième pattern : ignorer les warnings de la console. Chaque warning est un signal qui mérite d’être lu et compris. Troisième pattern : ne pas tester sur de vrais appareils. DevTools simule mais ne remplace pas un test physique sur un Android d’entrée de gamme avec une 4G dégradée. Documenter chaque décision technique majeure dans un fichier ADR (Architecture Decision Record) prend dix minutes et fait gagner des heures lors d’un incident ou d’un audit.
Pratiques avancées et outils complémentaires
Au-delà des patterns présentés, plusieurs outils et techniques complètent une maîtrise sérieuse du sujet. Premier axe : automatiser la qualité via une pipeline CI (GitHub Actions, GitLab CI) qui exécute tests, linting et audit de sécurité avant chaque déploiement. Cela évite 80 % des régressions introduites par des modifications hâtives. Deuxième axe : monitorer en production avec un outil comme Sentry pour les erreurs JavaScript ou New Relic pour les performances applicatives — la plupart proposent un free tier qui suffit pour démarrer. Troisième axe : documenter les décisions importantes dans un dossier docs/adr/ du projet, avec un format simple (contexte, décision, conséquences). Cette traçabilité paie quand un nouveau membre rejoint l’équipe ou quand un audit externe demande de justifier les choix techniques.
Ressources francophones pour approfondir
Plusieurs ressources gratuites en français permettent de monter en compétence rapidement. MDN Web Docs reste la documentation de référence, intégralement traduite pour la majorité des sujets. FreeCodeCamp propose des parcours de 300+ heures avec exercices interactifs et certificat. JavaScript.info (en français : fr.javascript.info) couvre le langage en profondeur. Grafikart.fr offre des centaines de tutoriels vidéos en français de qualité. Pour la pratique, contribuer à un projet open source via GitHub est l’investissement le plus payant à moyen terme — recruteurs et clients regardent les contributions GitHub avant le CV.
Pratiques avancées et outils complémentaires
Au-delà des patterns présentés, plusieurs outils et techniques complètent une maîtrise sérieuse du sujet. Premier axe : automatiser la qualité via une pipeline CI (GitHub Actions, GitLab CI) qui exécute tests, linting et audit de sécurité avant chaque déploiement. Cela évite 80 % des régressions introduites par des modifications hâtives. Deuxième axe : monitorer en production avec un outil comme Sentry pour les erreurs JavaScript ou New Relic pour les performances applicatives — la plupart proposent un free tier qui suffit pour démarrer. Troisième axe : documenter les décisions importantes dans un dossier docs/adr/ du projet, avec un format simple (contexte, décision, conséquences). Cette traçabilité paie quand un nouveau membre rejoint l’équipe ou quand un audit externe demande de justifier les choix techniques.
Ressources francophones pour approfondir
Plusieurs ressources gratuites en français permettent de monter en compétence rapidement. MDN Web Docs reste la documentation de référence, intégralement traduite pour la majorité des sujets. FreeCodeCamp propose des parcours de 300+ heures avec exercices interactifs et certificat. JavaScript.info (en français : fr.javascript.info) couvre le langage en profondeur. Grafikart.fr offre des centaines de tutoriels vidéos en français de qualité. Pour la pratique, contribuer à un projet open source via GitHub est l’investissement le plus payant à moyen terme — recruteurs et clients regardent les contributions GitHub avant le CV.
Lectures complémentaires
- Variables CSS (Custom Properties)
- Animations CSS sans JS
- Référence : MDN — prefers-color-scheme
- Accessibilité : WebAIM — Contrast Checker
- Pattern complet : web.dev — color-scheme
Besoin d’un site web pour votre activité ?
Conception complète : développement, nom de domaine et hébergement la première année, formation et support technique de 6 mois.
À partir de 350 000 FCFA