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:
HTML
<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:
CSS
#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:
CSS
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:
CSS
: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:
CSS
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:
CSS
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:
CSS
@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:
- This doesn't yet work on Firefox. Even if you enable
layout.css.has-selector.enabledin about:config. Support is coming soon™. - This doesn't remember your user's choice. So they'll need to toggle it on every page load.
- 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.
JAVASCRIPT
// 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); }); }
6 thoughts on “CSS only colour-scheme selector - no JS required”
@Edent
"This doesn't yet work on Firefox." Unfortunately, that kind of makes it a non-starter for sooo many projects.
| Reply to original comment on mastodon.social
@Edent Well this has given me some ideas for mischief. Big fan of playing with Pure CSS Things: https://css-orrery.netlify.app/The CSS Orrery
| Reply to original comment on mastodon.me.uk
@Edent once more browsers support :has this will be a very cool thing!
| Reply to original comment on jkpg.rocks
@blog will have to try this out with the has selector. Awhile back I played with a Firefox only feature called alternative stylesheets. It ended up needing JavaScript to play nice in other browsers. Would love to have a no-js version!
https://box464.com/posts/style-switchers/
| Reply to original comment on mastodon.social
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.)
More comments on Mastodon.
Trackbacks and Pingbacks
[…] 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 […]