Introduction
Je me souviens très clairement d'avoir lu la recommendation de ne pas utiliser de texte clair sur arrière-plan sombre. Je parle de l'époque d'Internet Explorer 6.0 que les moins de 20 ans ne peuvent pas connaître et à qui je peux garantir que c'était pas mieux avant.
Et ça vient pas de nulle part, on sait depuis les balbutiements du développement web que le mode sombre n'est pas meilleur pour les yeux, au contraire, ça dilate de la pupille, accentue les effets de halo et par conséquent demande plus de travail au cerveau.
Pourtant, depuis quelques années, tous les gens cools et chébrans utilisent le mode sombre et c'est courant de les voir se payer librement la tête de ces pauvres dino-développeurs qui utilisent le thème clair de Visual Studio.
C'est pas rare d'avoir un bouton pour basculer de mode sur les Internettes. Enfin... C'est étrangement absent de certains sites (je te regarde Wikipedia) bien qu'il existe toujours le mode lecture ou des extensions de navigateur pour ces cas là.
Si vous avez l'âme d'un hack3r vous pouvez même créer un mode sombre de wish avec la console (j'ai fait cette vidéo en 10 minutes, je suis au courant que c'est moche):
Si c'est pas mieux pour les yeux, pourquoi?
La mode. Et aussi la consommation des écrans OLED et AMOLED, lesquels sont très utilisés sur téléphones mobiles et autres appareils avec une modeste batterie.
Ces écrans allument ou éteignent indépendamment les pixels. Ceux qui sont éteints ne consomment pratiquement rien, ceux qui sont pleinement allumés consomment plus en moyenne que du blanc sur une autre technologie d'écran.
Les écrans classiques (dits LCD ou à cristaux liquide) utilisent un système de rétro-éclairage qui est toujours actif, même si la lumière est bloquée pour tous les pixels pour essayer de faire du noir (qui est du coup moyennement noir, la faiblesse majeure des écrans LCD).
Ces écrans consomment pratiquement la même chose pour une image noire qu'une toute blanche sauf si vous avez un écran avec des zones de micro-dimming, c'est à dire des zones où le rétro-éclairage peut être altéré indépendamment des autres.
Il y a très peu de chances que votre écran Medion volé au bureau possède ce type de technologie parce que ça coûte cher et de toutes manières l'option est parfois désactivée pour la bureautique parce que ça crée des changements de luminosité perceptibles qui peuvent être dérangeants.
L'autre raison majeur est dépeinte par le tableau suivant:
Il est 2h du matin, le programmeur est dans l'obscurité totale parce qu'il a acheté une seule lampe de bureau qui éclaire pas et c'est à peine si iel arrive à trouver son RedBull.
Son immense écran extra-large est tout le temps allumé et s'il ouvre une fenêtre navigateur sur un site avec un fond blanc, toute la pièce est brûlée par des gigajoules de photons quantiques sauvages bombardant allègrement ses yeux, qui piquent déjà pas mal en raison du manque de sommeil, se mettent à rouger et dessècher.
C'est la fin. Il n'y a absolument aucune autre option que de râler pour avoir des couleurs sombres partout. C'est pas comme si les écrans et ordis portables avaient un contrôle de luminosité. Non, c'est trop chiant, laissez moi consommer le max de WATTS non stop.
Voilà pourquoi. DE RIEN.
Détection du mode sombre global
Comme c'était trop la mode, les systèmes d'exploitation et navigateurs ont rapidement implémenté l'option de basculer leur interface en mode sombre.
Un Media Query CSS est supporté par tous les navigateurs courants depuis mi-2019-2020 et s'applique uniquement sur les clients qui ont le mode indiqué d'actif au niveau système d'exploitation:
@media (prefers-color-scheme: dark) {
/** Styles SOMBRES ici **/
}
Bien évidemment les règles de priorité CSS (dit aussi la cascade) s'appliquent, ce qui amène générallement à mettre cette section tout à la fin des styles, avec les autres media queries.
D'ailleurs, je le précise au cas où, ces styles n'annulent pas les autres qui auraient pu être déclarés plus haut, ils s'ajoutent en plus et les propriétés qui se retrouvent aux deux endroits suivront les règles de cascade (par ex. les styles et propriétés qui apparaissent plus tard ont priorité).
Bien entendu on peut aussi utiliser prefers-color-scheme: light.
Tout ça c'est bien joli sauf que ça ne permet pas du tout d'avoir un bouton ou une option directement sur le site pour changer de thème.
Le seul moyen d'enclencher ou pas le media query en question est de modifier le mode sombre/clair au niveau du système d'exploitation ou du navigateur.
De fait, Les navigateurs permettent générallement de choisir si vous voulez suivre le mode sélectionné au niveau système d'exploitation, ou un mode en particulier comme ici pour Firefox:
Mode sombre/clair automatique du pauvre
Même avec 0 octets de CSS, les navigateurs sont capables de modifier certaines couleurs par défaut selon le thème choisi au niveau système/navigateur.
Par exemple, l'arrière plan par défaut, il est blanc. Ben avec un certain tag meta de présent, il devient gris foncé. Et ouais.
Idem tous les contrôle de formulaires extrêmement moches que l'on connait tous, à savoir les horribles boutons et autres textarea, ils peuvent aussi automatiquement changer de thème sans avoir à ajouter du CSS.
Le tag en question est:
<meta name="color-scheme" content="light dark">
Avec cette valeur de content, les couleurs par défaut sont choisies selon votre thème navigateur/système.
Il est possible de forcer un thème ou l'autre en n'en mentionnant qu'un seul, par exemple:
<meta name="color-scheme" content="dark">
Change les couleurs par défaut en sombre d'office. C'est intéressant si votre site n'a qu'un seul thème, le sombre, auquel cas autant dire au navigateur qu'il doit tout mettre en sombre. Et vice versa si votre site n'a qu'un thème clair.
Je vous ai concocté un genre d'oeuvre d'art qui illustre ce mécanisme, une page avec absolument aucun style (elle est bonne). Non sérieusement, aucun CSS.
Le bouton "Changer de thème" ajoute simplement le meta tag s'il n'existe pas (ce qui devrait afficher tout en clair en tous cas chez moi et mon Firefox de communiste) en lui donnant la valeur "sombre".
Cliquer le bouton par la suite bascule de sombre à clair etc.
Il s'agit, dans l'idéal, d'ajouter ce meta tag avec les thèmes que votre site supporte, où vous pouvez l'utiliser pour ajouter un thème sombre "gratuit" sur des pages autogénérées sans CSS.
Proposer un bouton pour basculer de thème
Il y a plusieurs manières de procéder selon votre niveau de modernité du web préféré.
C'est aussi possible de le gérer "à l'ancienne" (bien que ça revienne à la mode): côté serveur en rafraîchissant totalement la page et en servant des fichiers CSS différents après un passage en mode sombre ou clair et en utilisant un cookie pour le tracer.
De nos jours, c'est courant d'équiper son site de variables CSS.
Ces variables sont souvent utilisées (souvent juste pour ça d'ailleurs) pour enregistrer les couleurs principales du site à un endroit central.
Le coup classique étant de les définir dans le sélecteur :root qui est le plus haut sélecteur possible (équivalent à "html"? J'ai jamais compris la différence):
:root {
--main-color: deeppink;
}
Puis ça s'utilise ainsi comme on dit à Liège:
.mon-beau-titre {
font-family: cursive;
color: var(--main-color);
}
Basculer une classe ou attribut sur html ou body
Le principe de base du basculement de thème consiste à utiliser JavaScript pour ajouter ou retirer une classe (ou un attribut, ça fait plus "hacker" je pense) sur un élément qui est parent d'à peu près tout, générallement html.
Petit exemple:
:root {
--text-color: #333;
--bg-color: #edebed;
}
html.dark-mode {
--text-color: #edebed;
--bg-color: #333;
}
body {
font-family: sans-serif;
color: var(--text-color);
background-color: var(--bg-color);
transition: all 0.3s;
}
.some-div {
background-color: #ddd;
padding: 1rem;
margin: 1rem 0;
}
html.dark-mode .some-div {
background-color: darkslategrey;
color: silver;
}
J'ai deux couleurs définies comme variables au niveau le plus haut possible.
Je redéfinis ces variables quand le tag html possède la classe .dark-mode.
Je définis quelques bidules sur body, notamment les couleurs d'arrière plan et de texte qui, je l'espère, changeront automatiquement avec le changement de variable.
Tant qu'on y est, on ajoute une chtite transition pour faire classe quand ça bascule de couleurs.
Vient ensuite un petit exemple de style particulier lorsque le thème sombre est activé, pour une classe inventée .some-div.
Cette mono ligne de JavaScript suffit ensuite pour passer d'un mode à l'autre:
document.querySelector("html").classList.toggle("dark-mode")
Petite démo extrèmement basique (vous pouvez cliquer droit et afficher la source si besoin):
Et là on a un moyen de passer d'un thème à l'autre mais:
- Le choix n'est retenu nulle part, le thème clair se remet à chaque recharge;
- L'éventuelle préférence utilisateur configurée au niveau système d'exploitation ou navigateur est totalement ignorée.
Combiner avec le media query CSS
Tentons de résoudre le problème numéro 2 cité plus haut et d'ajouter une activation automatique du thème qui correspondrait aux préférences système ou navigateur de l'utilisateur.
Comme d'hab il y a aussi plusieurs manières d'y parvenir.
La base: dupliquer tout dans un media query
Qu'est-ce qui nous empêche d'ajouter une section @media avec les mêmes styles que ceux du thème sombre?
En reprenant le super exemple présenté plus haut, ça donnerait ceci avec la section media ajoutée en bas:
:root {
--text-color: #333;
--bg-color: #edebed;
}
html.dark-mode {
--text-color: #edebed;
--bg-color: #333;
}
body {
font-family: sans-serif;
color: var(--text-color);
background-color: var(--bg-color);
transition: all 0.3s;
}
.some-div {
background-color: #ddd;
padding: 1rem;
margin: 1rem 0;
}
html.dark-mode .some-div {
background-color: darkslategrey;
color: silver;
}
@media (prefers-color-scheme: dark) {
:root {
--text-color: #edebed;
--bg-color: #333;
}
.some-div {
background-color: darkslategrey;
color: silver;
}
}
Le sélecteur ":root" aurait pu être "html", je trouvais ça plus clair de cette manière pour illustrer le côté "duplication".
J'aime pas trop ce plan parce qu'il y a de l'info redondante et je vois pas trop qui aurait envie de gérer ça manuellement, du coup il faut ajouter un plan avec des inclusions en PostCSS ou SCSS et je suis même pas sûr que ça serait efficace.
Cette solution elle pue la fesse de chat.
JavaScript to ze rescue!
Au tout début de cette section j'exposais l'idée d'ajouter ou retirer une classe spécifique à l'élément html ou body pour activer le thème alternatif (générallement le mode sombre, le clair étant celui par défaut).
Il est possible de vérifier la préférence système/navigateur active en JavaScript, et décider ensuite d'ajouter ou non la classe qui active le thème alternatif.
Exemple de cette détection en pratique:
Ce qui implique une API bizarre du navigateur disponible globalement nommée matchMedia:
// Ceci est le code JS de l'exemple montré plus haut
const main = document.querySelector('main')
const mediaQueryObj = window.matchMedia('(prefers-color-scheme: dark)');
if (mediaQueryObj.matches === true) {
main.textContent = "Préférence mode sombre détectée"
} else {
main.textContent = "Préférence mode clair détectée"
}
En gros on teste si un certain media query CSS s'applique ou non en ce moment.
Plutôt que d'afficher du texte comme je le fais ici selon la préférence, on pourrait basculer la classe CSS du mode sombre sur l'élément html ou body. Une action à effectuer dès le chargement de la page (sauf si on implémente aussi un mécanisme pour se souvenir d'une éventuelle préférence utilisateur alternative).
Et si JavaScript est désactivé?
Ben ça marche plus. Je suis bien d'accord que la moitié du web ne marche plus non plus mais idéalement on aimerait bien avoir un maximum de fonctionnalités opérationnelles sans JavaScript et sur mon blog bizarre on aspire toujours à l'idéal **bruit de prout** (ça faisait longtemps).
Il y a bien une solution qui n'implique pas de dupliquer tous les styles du mode sombre (ou clair, selon votre mode par défaut) dans un media query, et elle implique le tag link.
Oui, vous savez, ce tag qui permet de charger un fichier CSS externe depuis head dans un document HTML.
On l'oublie souvent mais link possède un attribut media qui permet de définir un media query à appliquer aux styles qui sont dans le fichier.
Par exemple:
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">
Charge le fichier dark.css et le considère comme s'il était à l'intérieur de:
@media (prefers-color-scheme: dark) {
/** Contenu du fichier ici **/
}
C'est cool mais en quoi ça nous aide à pouvoir basculer de thème en JavaScript tout en ayant le support du thème par défaut système/navigateur sans devoir dupliquer tous les styles?
Et bien mes amis, comme il y a un tag link qui est présent, on peut en récupérer une référence en JavaScript et le retirer/re-ajouter voire utiliser l'attribut disabled qui, à ma modérément grande surprise, fonctionne sur les tags link et empêche effectivement les styles concernés de s'appliquer.
Il s'agit de séparer ses styles en au moins deux fichiers:
- Les styles communs à tous les thèmes, avec toutes les propriétés qui seront les mêmes — Comprend aussi les variables CSS pour les couleurs par défaut;
- Les styles du mode sombre ou du mode qui-n'est-pas-par-défaut — Avec les variables CSS qui vont bien.
L'ordre de chargement est important pour la surcharge des styles par défaut avec par exemple ceci:
<link rel="stylesheet" href="common.css">
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">
On peut facilement récupérer l'élément link avec un querySelector sur un de ses attribut. Vous pourriez d'ailleurs ajouter votre propre attribut data-theme ou similaire pour faciliter cette tâche.
Voici une exemple qui utilise le nom du fichier (attribut href) et désactive le thème sombre immédiatement après:
const darkLinkEl = document.querySelector("link[href='dark.css']")
darkLinkEl.disabled = false
Reste plus qu'à ajouter un peu plus de logique dans tout ça. Voici un exemple très basique:
Le résultat est évidemment le même qu'avant sauf que le thème par défaut du système sera respecté et qu'on a pas dû dupliquer tous les styles.
L'inconvénient? Il y a une ou deux requètes en plus pour avoir les fichiers CSS.
Fût un temps où j'étais assez extrème avec la minification et j'aurais vraiment pas aimé un fichier de plus pour mon blog qui a deux visiteurs par jour mais c'est en réalité courant d'avoir, par exemple, deux fichiers JS, l'un avec les librairies et l'autre avec le code de l'application.
Et puis prout HTTP/2 et HTTP/3 sont censés aider pour ces situations, on n'est pas à une requête près (gnnnnnn).
Petite solution tout en un
J'ai entendu dire que ça faisait très longtemps que j'avais plus parlé de Web Components.
J'aime bien les utiliser depuis ~2022 parce qu'on peut facilement les intégrer à n'importe quel framework. Et je suis pas le seul à les utiliser, j'en vois de plus en plus sur le web (exemple: Youtube est composé de ~80% de web components).
Une des équipes de Chrome a créé un web component un peu moche, mais très complet et personnalisable si on est motivé.
Pas comme mon très célèbre web component qui n'est pas trop personnalisable en interne.
Les liens vers la doc et la demo sont sur leur répositoire sur Github.
Ils utilisent la techique des fichiers CSS multiples et d'en mettre l'un ou l'autre en disabled comme je l'avais présenté plus haut (je leur ai d'ailleurs pompé cette technique hein, au cas où quelqu'un se posait la question).
C'est possible de déclarer directement le web component sur n'importe quelle page sans devoir utiliser le paquet npm en passant par unpkg. Vous ajoutez juste ça à votre head (doit être chargé avant la page pour éviter les flashs d'un style avant de basuler à un autre):
<script type="module" src="https://unpkg.com/dark-mode-toggle"></script>
Ils utilisent trois fichiers CSS, un commun chargé en premier, puis le clair et le sombre. J'ignore pourquoi le thème par défaut n'est pas intégré au commun mais j'imagine que c'est plus flexible comme ils font, avec un fichier en plus à télécharger.
Dans ce cas là il ne faut pas non plus spécifier les variables de couleurs dans le fichier CSS commun, vous pouvez les laisser comme ceci (joue le même rôle que le tag meta mentionné plus haut):
:root {
color-scheme: light dark;
}
On se retrouve alors avec quelque chose de ce genre dans le tag head du document:
<link rel="stylesheet" href="common.css">
<link rel="stylesheet" href="light.css" media="(prefers-color-scheme: light)">
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)">
<script type="module" src="https://unpkg.com/dark-mode-toggle"></script>
Si vous voulez un bouton plus stylé, cette page de démo en a un en bas à droite.
En bonus le composant permet de conserver la préférence utilisateur dans le localStorage.
Je l'ai intégré de manière extrêmement élégante sur mon expérience de re-design de blog qui dure depuis 10 ans:
Force m'eut été de constater que trouver de bonnes couleurs pour le thème sombre est loin d'être simple.
Références
Je mentionne très rarement (jamais) mes références, générallement pour cause de pure flemme.
Tout est en anglais évidemment, c'est la meilleure langue de départ pour de la recherche technique.
Voici quelques articles que vous pouvez parcourir pour aller plus loin:
- Bon résumé bien court (pas comme cet article quoi) de ce que je souhaitais réaliser: https://lukelowrey.com/css-variable-theme-switcher
- Un guide assez complet sur CSS Tricks, mentionne même des filtres à éventuellement appliquer sur les images pour qu'elles suivent le thème sombre ou clair: https://css-tricks.com/a-complete-guide-to-dark-mode-on-the-web
- L'article qui accompagne le projet de theme switcher décrit dans la section précédente: https://web.dev/articles/prefers-color-scheme




Commentaires
Il faut JavaScript activé pour écrire des commentaires ici