CSS only colour-scheme selector - no JS required


Yesterday I wrote about a lazy way to implement a manual dark mode chooser. Today I'll show you a slightly more sensible way to do it. It just uses CSS, no need for JavaScript.

Here's a scrap of HTML which present a dropdown for a user to choose their colour scheme:

<select id="colour-mode">
  <option value="">Theme Selector</option>
  <option value="dark">Dark Mode</option>
  <option value="light">Light and Bright</option>
  <option value="eink">eInk</option>
</select>

It will look something like this:

Modern CSS gives us a way to see which of those options have been chosen by the user:

#colour-mode option:checked[value="dark"]

That can be combined with the new :has() CSS pseudo class. This allows the CSS to say "If the body has a checked elements with this specific value, then apply these CSS rules" - like so:

body:has( > #colour-mode option:checked[value="dark"] ) {
  background: ...
}

OK! So, depending on which option the user selects, the CSS can be made to do all sorts of weird and wonderful things. But, that will require...

CSS variables

Here's some CSS which will set various colours for light mode and dark mode. Then it sets the default colours to the light mode:

:root {
  /* Light mode variables */
  --light-background: beige;
  --light-text: #000;
  --light-bg: #0F0;

  /* Dark mode variables */
  --dark-background: #000;
  --dark-text: #FFF;
  --dark-bg: #FF0;

    /*  Default variables */
  --background: var(--light-background);
  --text: var(--light-text);
  --bg: var(--light-bg);
}

So the rest of the CSS can have things like:

p {
   color: var(--text);
}

The <p> will be set to use the colour from --text which, at first, is the same as --light-text.

That can be changed with both :has() and :checked like so:

body:has( > #colour-mode option:checked[value="dark"] ) {
  --text: var(--dark-text);
}

That says "If the body element has a child which has the ID "colour-mode", and if "colour-mode" has a child option with the checked value of "dark", then set the --text variable to be the value of --dark-text.

String enough of those together and that will make a pretty capable theme switcher!

User Preferences

Some browsers will know whether their user has a preference for dark or light mode. Perhaps the user has set their phone to dark mode, or flipped a switch somewhere for light mode. This preference can be determined in CSS using the prefers-color-scheme media feature.

A site can set the default value of the colour variables like so:

@media (prefers-color-scheme: light) {
  :root {
    --text: var(--light-text);
  }
}

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

Demo

Caveats

A few issues:

  1. This doesn't yet work on Firefox. Even if you enable layout.css.has-selector.enabled in about:config. Support is coming soon™.
  2. This doesn't remember your user's choice. So they'll need to toggle it on every page load.
  3. Choosing a sensible colour scheme means you should test it for accessibility.

Saving the selection

Sadly, this does require JS. This uses localstorage rather than a cookie. If a user doesn't have JS enabled, this will gracefully degrade; the theme will follow the user's preferences, switching will work but won't be remembered.

//  Get the theme switcher
var themeSelect = document.getElementById('theme');

//  If a theme has previously been selected, set it
if (localStorage.theme) {
    themeSelect.value = localStorage.theme;
}

//  Listen for any changes and save them
if(themeSelect) {
    themeSelect.addEventListener('change', function(event){
        localStorage.setItem('theme', themeSelect.value);
    });
}

Further Reading


Share this post on…

6 thoughts on “CSS only colour-scheme selector - no JS required”

    1. Back when Opera had its own engine (called Presto), it used to support alternate stylesheets for web pages. Sounds similar to this Firefox-only feature. (I don't know if modern-day Opera based on Chromium supports it, never bothered to check.)

      Reply

Trackbacks and Pingbacks

  1. […] Przełącznik light/dark mode w czystym CSS bez JShttps://shkspr.mobi/blog/2023/10/css-only-colour-scheme-selector-no-js-required/INFO: Zazwyczaj zmiana trybu strony z “dziennego” na “nocny” następuje przy lekkiej pomocy […]

What are your reckons?

All comments are moderated and may not be published immediately. Your email address will not be published.Allowed HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <p> <pre> <br> <img src="" alt="" title="" srcset="">