How I added dark mode to this website
People want dark mode on websites and in apps for different reasons. Some simply like how it looks, while others use it as an accessibility tool or to save power. With this post I will explain how I added dark mode to this website.
There are several ways to let the user toggle between light and dark mode. I want this site to use as few scripts as possible, so a script-based toggle button as suggested and used by many, is not optimal. Luckily, the MediaQueries Level 5 specification introduced the prefers-color-scheme media query, making it easy to add different styles for light and dark mode and then let the browser and OS handle the actual toggling. At the time of writing this post, this new media query is supported by the current versions of Safari, Firefox, and Chrome on both desktop and mobile. Neither Edge nor IE supports this feature, but I can live with that.
I use PostCSS to optimise my CSS files, with the postcss-custom-properties plugin to process CSS custom properties and replace them with their actual value. My initial thought was that I could add the media query for dark mode at the top of my stylesheet and redefine the color properties there, like this:
:root {
--background: #fff;
--color: #000;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #000;
--color: #fff;
}
}
body {
background-color: var(--background);
color: var(--color);
}
It seems, however, that postcss-custom-properties
will only work withproperties declared in the main :root
, and ignores properties
declared or redeclared elsewhere. This means that the redeclaration of the
--background
and --color
properties in the media query never gets
processed.
Reading the issues in the postcss-custom-properties repository made me aware of
another plugin, called
postcss-css-variables
. The main difference between
these plugins is that the first restricts declarations of custom properties to
the :root
selector, as we noticed, while the latter allows properties to be
declared inside any rule in whatever selector. Exactly what I need!
This functionality comes with a few cavecats , but as I will only be using this to redefine a few selected properties and use them in a single media query, these cavecats are not relevant for my usage. There is one small bug that affects me, though. Notice in the CSS below, how there are multiple media queries with only one rule each, even when they refer to the same selector:
body {
background-color: #fff;
color: #000;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
}
}
@media (prefers-color-scheme: dark) {
body {
color: #fff;
}
}
This is a known bug with no known (or easy) fixes, that has been
reported
multiple
times
. Luckily, the
cssnano
plugin for PostCSS, with the mergeRules
optimisation enabled, will
merge media queries into one per selector. Perfect!
After finishing everything up, the result is a website that automatically changes between light and dark mode according to the user’s own device settings.