Creating a dark mode theme

While updating this site, I thought it was about time it supported a dark theme. The simplest way is to use CSS variables (custom properties) to change the colours based on user preference.

Support for CSS variables is decent at over 96% currently. The only notable exceptions are IE, which shouldn’t be supported as it no longer receives security updates, and Opera Mini in “extreme” data saving mode, which lacks many modern features and will break most websites.

Checking the logs, I’m confident all visitors to this site will be using a browser that supports CSS variables.

Using CSS variables

The first step is to move all colours into CSS variables and have them switch to the dark theme when prefers-color-scheme is set to dark:

:root {
  --font-color: #ddd;
}
    
@media (prefers-color-scheme: dark) {
  :root {
    --font-color: #ddd;
  }
}

html {
  color: var(--font-color);
}

The color-scheme property

The CSS variables technique works, but if applied manually, say via a class, the browsers default styling will still be light.

The color-scheme property solves this. It specifies the colour scheme of the default browser styling to apply to an element and its descendants. The default styling changes things like the default background colour, colours of controls like scrollbars and form elements, etc.

The value of color-scheme can be either light, dark or both. If both are specified, then the first one is treated as preferred by the website, but the second may be used if preferred by the user.

By setting the property to dark on the :root, it will cause the default browser style for things like scrollbars and form elements to be dark:

:root {
  /*
  Instead of explicitly specifying both, could have:

  color-scheme: light dark;

  which would prefer light but would switch to dark
  if preferred by the user.
  */
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
  }
}

The color scheme can also be specified in a meta tag:

<meta name="color-scheme" content="light dark">

The above will default to light unless the user prefers dark.

I’ve chosen to explicitly specify it for this site as I intend to add a toggle button and want to set the value based on that.

Handling print

Printing a dark page with light text would use a lot of ink. It’s unlikely anybody would ever want to, so browsers attempt to convert the dark theme to light before printing.

The conversion can have some issues, though, so it’s better to use the light theme for printing. One way to do it is to exclude the dark theme from printing by adding a parent media query to exclude print:

@media not print {
  @media (prefers-color-scheme: dark) {
    :root {
      /* custom properties as before... */
    }
  }
}

Unfortunately, it has to be a nested media query as the not will apply to the whole query, not just the print portion. For example:

@media not print and (prefers-color-scheme: dark) {}

would be interpreted as:

@media not (print and (prefers-color-scheme: dark)) {}

If media queries supported or, then it would be possible to do it without nesting, but sadly they don’t currently.

The theme meta tag

The theme-color will need to be darker when in dark mode. Luckily, meta tags support having a prefers-color-scheme media query.

Browsers will use the first matching theme-color meta tag so both meta tags need to have a media query, otherwise the first would be applied:

<meta name="theme-color" content="#ffffff"
	media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#000000"
	media="(prefers-color-scheme: dark)">

Theoretically, it could cause an issue if a browser supported theme-color but not prefers-color-scheme. A default theme-color without media query can be added after the others to prevent this. However, prefers-color-scheme has much better browser support, so it isn’t worth doing in practice.

Conclusion

This site now has a dark theme. It might still need some tweaking to make it look nice, but I’m happy with it so far.

The next step is to implement some form of toggle button for it.

Comments