Dark Mode, and other site improvements
I’ve been listening to the Accidental Tech Podcast for years. They recently had a members-only special about the technology they use for their personal websites. Building customized, Rube Goldberg-esque systems for managing a website is a time-honored time sink for many, myself included. And since an earlier version of this site used a system developed by one of the podcast’s hosts (and inspired its look and feel), I was looking forward to the episode.
Near the end, John Siracusa nerd sniped me with a discussion about Dark Mode. I thought, oh, Dark Mode. I don’t really use that, but I can see where my site might be blindingly bright if someone’s browsing it late at night. So I should probably investigate this.
I mean, how hard could it be?
Basic Approach
Ideally, this would be a completely passive solution. That is, the browser would recognize that the user was currently in “dark mode,” and change the site to a different set of formatting preferences that makes it look good in that mode. It’d work without me having to worry about custom scripts, browser-specific hacks, or other hard-to-manage technology.
Fortunately, there’s a very simple mechanism built into CSS (cascading style sheets) technology, that lets this happen pretty much automatically. There are two specific selectors one can … er… select for:
@media screen and (prefers-color-scheme: dark)
- selects a block of CSS code if the browser is currently in dark mode@media screen and (prefers-color-scheme: light)
- selects CSS code for when it’s in light mode
So now…I just need to find…all the selectors that deal with colors. And add this switch, and the appropriate color or font choices, for every. single. one. I’m worried that this could be tedious.
Rebuild!
I first set up this website engine 8 years ago. I was only just getting started with Hugo at the time – so some of my choices were probably wrong. Or at least, less than ideally implemented. After a few days of updates, I think I actually did a halfway decent job, but there are still many things that can be improved.
So as I worked on Dark Mode, I also found myself refactoring, tweaking, and generally improving the site design across the board. Someday, maybe I’ll do a more extensive post for how the theme works, but for now, I’ll stick to just the CSS changes I made.
The general approach I came up with was to think about two key parts of the site design:
-
First, there’s layout and structure. How is the site split into a main content section and a sidebar? How does the header and footer work? All of these elements – a header with a title, a subtitle, a menu, my avatar, etc. – get defined in CSS as far as locations, widths, margins, alignment, etc.
-
Then, there’s fonts, and text and background colors. In this section, I’ll repeat many of the same selectors I used for layout, but only include color or font changes (or anything else that switches with light/dark mode). And to make it easier, I can put all the dark mode settings in one block (with one
prefers-color-scheme
selector block), and all the light mode settings in another. (this avoids the previously feared tedious task of putting a light/dark switch on every single color entry)
So basically, my CSS now looks sort of like this (I just picked a few example selectors for discussion):
1body {
2 font-size: 11pt;
3 font-family: Verdana, Helvetica, Arial, Times, sans-serif;
4 font-weight: 300;
5 margin: 0;
6 padding: 0;
7 word-wrap: break-word;
8}
9
10h1, h2, h3, h4, h5, h6 {
11 font-weight: 400;
12}
13
14h1 {
15 font-size: 20pt;
16}
17
18h2 {
19 font-size: 18pt;
20}
21
22h3 {
23 font-size: 16pt;
24 font-weight: 800;
25}
26
27
28@media screen and (prefers-color-scheme: dark) {
29 body {
30 color: #ddd;
31 background-color: #1e1e1e;
32 }
33
34 h1, h2, h3, h4, h5, h6 {
35 color: #ddd;
36 }
37
38 .title {
39 color: #ddd;
40 }
41}
42
43@media screen and (prefers-color-scheme: light) {
44 body {
45 color: #222;
46 background-color: #fff;
47 }
48
49 h1, h2, h3, h4, h5, h6 {
50 color: #222;
51 }
52
53 article header .title {
54 color: #222;
55 }
56}
(Is this the most efficient, cleanest, best-organized CSS? Absolutely not. Does it work? Apparently so. Do I promise to try and improve it? Eventually, yes… Maybe.)
This kind of exemplifies the approach I was taking. First, I define the layout of the page – font sizes, margins, etc. (Of course, none of the examples I picked here have anything to do with margins or padding or anything…)
Then, I define the colors, for the two different modes. This is accomplished in the lines highlighted above. If the page is being displayed on a screen (as opposed to, say, being printed), and the preferred color scheme is set to “dark,” then the first block of color definitions are used. If scheme is “light,” then the browser uses the second set of definitions.
What happens if it’s neither light nor dark? Hm. Again, my lack of deep CSS knowledge is obvious. I’m not sure if all browsers will always return one or the other of those values. Certainly, older browsers won’t return anything – and so they’d just get default colors, whatever they were.
Maybe I need to move the light mode colors to the layout section, as the overall default settings for the site, and handle just the dark mode colors as the exception.
Dealing with Images
The text isn’t the only thing that can appear too bright when in dark mode. Some images, too, could be better off switched. There’s a mechanism for that, but it requires having two copies of every image you want to support both modes.
Back to the member special, and Siracusa’s sniping. I visited his Hyperspace documentation, which has two copies of every screenshot, one for light mode, and one for dark. (Amazing attention to detail!) The way he accomplished this was using the picture
tag, and source
tags with media
selectors:
1<picture>
2 <source media="(prefers-color-scheme: light)" srcset="images/screenshot-1.0.png">
3 <source media="(prefers-color-scheme: dark)" srcset="images/screenshot-1.0-dark.png">
4 <img src="images/screenshot-1.0.png" width="762" height="519" alt="Hyperspace screenshot">
5</picture>
Here, the picture
tag wraps the entire image block. Two different sources are given, depending on whether the current color scheme is light or dark. Finally, an img
tag provides a fallback way to load the plain image in browsers that don’t support the light/dark switch.
This works, but, as I said, it requires creating two different images each time. And, not gonna lie, that looks like a lot of typing, though in Hugo that could be drastically shortened using shortcut codes.
Really, the only place where I noticed this was a problem is in the little icons I have next to blog post links, showing the post type: a plain post, cross-post from another blog, reference, etc. Those looked pretty bad for a while, as I finished adding in dark mode support.
Turns out, for very simple images (just like these icons), you can have the browser just invert the colors. Here’s how the img
tag looks in the generated HTML:
<img alt="post type icon" src="/img/post.png" class="color-invertable type-icon">
And the CSS code (from the prefers-color-scheme: dark
block) that actually does the inversion:
.color-invertable {
filter: invert(100%);
}
Now, any image that includes the color-invertable
class will have its colors reversed when displayed in dark mode.
Turns out, this looked just fine for my purposes. I updated all the occurrences of these post type
icons to use the invertable class, and declared victory.
Getting fancier?
Are there better ways to make this happen? Possibly. There might even be ways to simplify the color changes down to just a few lines with a lot of selectors. Also, I think I’ve seen mention of color variables. I could define all the light and dark colors in one place, then just refer to them later by variable name. That way, if I ever want to update the look of the site, I’d just have a few lines at the top of the file to change.
On the other hand, that kind of major overhaul isn’t likely to happen very often, so it’s probably not worth the effort to add that layer of abstraction.
My CSS files remain a disorganized mess, and simply cleaning them up is on my ToDo list. Maybe I’ll enlist the help of some real HTML/CSS nerds (my brother, for example). But most likely, I’ll put this on the backburner, spend a week on it 6 months from now, and then leave it at 80% finished for another 5 years.
Meh. As long as the content is viewable and useful, I’m satisfied.
Overall, though, it was fun learning about and implementing dark mode. It does seem to make a big difference, and it wasn’t difficult to add at all.
UPDATE: Ironically, I hadn’t actually tested this post in dark mode until after it went live. And turns out, the syntax highlighting plugin that was being used for the HTML and CSS examples…didn’t support dark mode. Or, rather, I hadn’t selected an appropriate color scheme for dark mode.
One of the first hits I found had a good suggestion for how to fix this, and so I have. I didn’t do any of the javascript switching that they suggested; I just used the hugo gen chromastyles --style=onedark
command to create the relevant CSS code, and dropped that into my CSS file’s dark mode block.