DEV Community

Ayc0
Ayc0

Posted on

Light/dark mode: Corrections

In this post, I want to fix some mistakes I made in other posts, and also include new elements I discovered on this topic since I wrote them.

When this will be released, all other related posts will also be edited to avoid mentioning those errors. I wanted to reference all of them for posterity.

CSS color scheme

In this post, I only mentioned the meta tag with the name color-scheme:

<meta name="color-scheme" content="light dark" />
Enter fullscreen mode Exit fullscreen mode

But I forgot to mention that this can also be set in the CSS (see MDN):

:root {
  color-scheme: light dark;
}
Enter fullscreen mode Exit fullscreen mode

I also wrote:

Note: this will work on both Chrome and Safari, but not Firefox

This has been fixed in Firefox 96 🎉 (see caniuse).

Native system colors

In this post, I only mentioned the use of color-scheme. But there is also another powerful tool available to us: native system themed colors.

In CSS, you can say that a color should follow the system color for multiple semantic elements, like LinkText, or Canvas (background color):

A few examples of system colors

The best is that those colors will have automated variants in dark mode (which is why they should have been mentioned in the “Lazy way” post).

For the whole list of system colors, and their semantic meaning, you can out the MDN page.

:root with class names

In this post, I wrote:

And as we are using classnames, we cannot use :root as before.

This is wrong 😕. When we read its MDN page, it says:

The :root CSS pseudo-class matches the root element of a tree representing the document. In HTML, :root represents the <html> element and is identical to the selector html, except that its specificity is higher.

After some tests, I can confirm that the following works fine:

:root.dark-mode {
  /* Works great! */
}
/* Equivalent to html but with a greater specificity */
html.dark-mode {
  /* Works great! */
}
Enter fullscreen mode Exit fullscreen mode

:root has specificity of (0, 1, 0), and html has a specificity of (0, 0, 1).

Using data attributes instead of class names

In this post, I mentioned that we were using 2 classes .light and .dark. And that we were using this function to control those classes:

const colorScheme = document.querySelector('meta[name="color-scheme"]');
function applyTheme(theme) {
  document.body.className = theme;
  colorScheme.content = theme;
}
Enter fullscreen mode Exit fullscreen mode

The issue with it, is that it overrides all classes set on the body. This is fine for this post, as we don’t have any other classes, but it may not be in your own application.

A more realistic function would be something like:

const colorScheme = document.querySelector('meta[name="color-scheme"]');
function applyTheme(theme) {
  document.body.classList.remove('light');
  document.body.classList.remove('dark');
  document.body.classList.add(theme);
  colorScheme.content = theme;
}
Enter fullscreen mode Exit fullscreen mode

You can see that it's a bit tedious to have to remove all classes, especially if you start to add more themes, like low/high contrast, etc.

A better solution would be to use data attributes, to which we can add the correction we did for the color-scheme, and for the root :root:

:root[data-theme="light"] {
  color-scheme: light;
  --text: black;
  --background: white;
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --text: white;
  --background: black;
}

body {
  color: var(--text);
  background: var(--background);
}
Enter fullscreen mode Exit fullscreen mode

And to set it:

function applyTheme(theme) {
  document.documentElement.dataset.theme = theme;
}
Enter fullscreen mode Exit fullscreen mode

(document.documentElement is the <html> node, see on MDN)

Real time system mode

In this post, I explained how to use a custom picker and how to use a system mode.

I forgot to say that this system mode won’t follow the current theme users have on their machine. Instead it just computes this theme when the mode is picked.

This mechanism is more complicated and can be explained in its own post. But in the meantime, the React implementation includes this feature:

Top comments (0)