[] Dark Mode

Simple CSS Theme Switcher

(posted in tech)

I spent a bit of time this evening implementing a new non-dark theme for this site, since it’s a common complaint I see in comment threads about my articles and I’m tired of getting sucked into dark vs. light mode religious arguments. Can’t we all just get along? My solution is to give users a choice. Go turn off that little check box in the upper right corner if you don’t like my dark theme (you’ll need JavaScript and local storage enabled for my domain for it to work).

Here’s a quick summary of how I implemented it. (The actual styling I did by hand, and consisted primarily of swapping the foreground and background colors and then darkening all of the accent colors used in my original theme to make them less washed-out looking against the lighter background.)

Alternate Stylesheets

A lot of browsers already support theming to some degree through the use of alternate stylesheets. For example, this site now loads two stylesheets, a main and an alt:

1
2
<link id='style' rel='stylesheet' href='/assets/style.min.css' title='main'>
<link id='style-alt' rel='alternate stylesheet' href='/assets/style-light.min.css' title='alt'>

Even without any JavaScript at all, just giving the stylesheets title tags and specifying one as rel='alternate stylesheet' enables the ability for some browsers to switch between them. For example in Firefox if you hit alt to show the menu you’ll see that both styles are listed in the View->Page Style menu, and by switching between them you can go between light and dark mode (not sure if Chrome can do this natively, but there are extensions that provide the same functionality).

Obviously nobody is actually going to use that, because there’s no kind of visual indicator that an alternate stylesheet is available and most users probably don’t even know that’s a possibility. It’s also not sticky–as soon as the user navigated to a new page the theme would revert back to the main one and the user would have to manually switch it back to alt using the menu every time. This is obviously not ideal.

The nice thing about using this as a base, however, is that the browsers will download and cache both stylesheets even though it’s only using one, which allows for quicker theme switching when we do implement an actual on-page control.

Toggle Switch

I store whether the light theme is enabled or not using localStorage in a value called light which either exists and is true or does not exist. Here’s the function I use to toggle it on and off:

1
2
3
4
5
6
7
8
9
10
11
12
13
function switchTheme() {
if (window.localStorage) {
if (window.localStorage.getItem('light')) {
window.localStorage.removeItem('light');
document.getElementById('style').setAttribute('href', '/assets/style.min.css');
document.getElementById('style-alt').setAttribute('href', '/assets/style-light.min.css');
} else {
window.localStorage.setItem('light', true);
document.getElementById('style').setAttribute('href', '/assets/style-light.min.css');
document.getElementById('style-alt').setAttribute('href', '/assets/style.min.css');
}
}
}

You can see that all it’s doing is setting or removing the localStorage value and swapping the href attribute of the main and alt stylesheets. I assigned this function to the onclick of my Dark Mode check box. (This site also has some code in there to add the x to my Dark Mode checkbox, but I removed that from the example since it’s inconsequential to the actual theme switching.)

Loading the Theme Initially

The only piece missing now is having the site load the correct theme when a page first loads. Without that pages would always load with my dark theme even if the light theme was enabled. My first attempt to solve this was to throw a script tag immediately below the two stylesheets that looked something like this:

1
2
3
4
5
6
<script>
if (window.localStorage && window.localStorage.getItem('light')) {
document.getElementById('style').setAttribute('href', '/assets/style.min.css');
document.getElementById('style-alt').setAttribute('href', '/assets/style-light.min.css');
}
</script>

The problem with that approach was that since the pages generally load very quickly (especially when cached), they would still render in dark mode for a brief moment before flashing to light mode, which looked very annoying to me. After trying out a few other methods that didn’t work at all, I settled on replacing the stylesheets in my head block entirely with this script and noscript code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
if (window.localStorage && window.localStorage.getItem('light')) {
document.write(`
<link id='style' rel='stylesheet' href='/assets/style-light.min.css' title='main'>
<link id='style-alt' rel='alternate stylesheet' href='/assets/style.min.css' title='alt'>
`);
} else {
document.write(`
<link id='style' rel='stylesheet' href='/assets/style.min.css' title='main'>
<link id='style-alt' rel='alternate stylesheet' href='/assets/style-light.min.css' title='alt'>
`);
}
</script>
<noscript>
<link id='style' rel='stylesheet' href='/assets/style.min.css' title='main'>
<link id='style-alt' rel='alternate stylesheet' href='/assets/style-light.min.css' title='alt'>
</noscript>

This causes the browser to render the correct stylesheet paths on the first pass before the DOM even loads, so the appropriate theme is applied immediately with no flicker.

If you’re a noscripter and have JavaScript disabled, you’re stuck in Dark Mode unless you use your browser’s built-in theme switcher to change to the alt stylesheet every time you load a page or use your browser’s built-in Reader mode. I don’t feel too bad for you; Dark Mode is better anyway and anyone who claims otherwise is clearly lacking all common sense, ignorant of facts and science, and likely a paid shill for the anti-dark-mode criminal cabal.

Short Permalink for Attribution: rdsm.ca/h9sqw

Comments