DEV Community

loading...

Light/dark mode: user input

Ayc0
I try to build good tools for JavaScript and React 😅
・Updated on ・2 min read

In the previous post, we saw how to use CSS variables to adapt the display to user system preferences.

But users of your website cannot change their theme directly from the website, they have to change their system mode to change it. Which can be a bit annoying when you want your OS to be in light mode and the website in dark mode for instance.

The CSS

The easiest is to apply a classname to the body light/dark. And as we are using classnames, we cannot use :root as before.

The CSS is still fairly simple:

body.light {
  --text: black;
  --background: white;
}
body.dark {
  --text: white;
  --background: black;
}

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

The JS

We’ll have to store the user preference for future visits to the website. You can do that with the method you prefer:

  • localStorage (if everything is done in the frontend)
  • cookie (if you want to have access to it from the backend)
  • remote database (if you want to apply the same theme to multiple devices)

If you store the preferences in a remote database, I'd still recommend to double save it in a cookie/localStorage, because we'll see later how to avoid blinks when loading the pages. And this needs synchronous access to the stored value.

I'm gonna stick with localStorage here, because it's the easiest to deal with, but it doesn't really matter for this example.

Reading and writing the theme

We can use this couple of function as first class getters/setters of the theme:

function getTheme() {
  return localStorage.getItem('theme') || 'light';
}
function saveTheme(theme) {
  localStorage.setItem('theme', theme);
}
Enter fullscreen mode Exit fullscreen mode

Setting the theme

As we only used a classname on the body, applying only corresponds to setting the classname on it.

Note: I'd still recommend setting the <meta name="color-scheme"> to content="light" and content="dark" for native inputs.

This leaves us with this function:

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

Assembling the whole ensemble

Now that we have all the elements, this is basically like legos: we need to assemble everything.

const themeToggler = document.getElementById('theme-toggle');

let theme = getTheme();
applyTheme(theme);

themeToggler.onclick = () => {
  const newTheme = rotateTheme(theme);
  applyTheme(newTheme);
  saveTheme(newTheme);

  theme = newTheme;
}
Enter fullscreen mode Exit fullscreen mode

Note: if you don't want any blink when users will load the page (seeing an empty white page when reloading the page for instance while they picked a dark mode for your website), it's important that this JS is executed in a blocking way, so that browsers won't render the html/css without having first computed this JS and applied the classname on the body.

Discussion (0)