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:
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 option
s 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:
CSSbody: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:
CSSp {
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:
CSSbody: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.enabled
in 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);
});
}
James 🌈💜 said on mastodon.social:
@Edent
"This doesn't yet work on Firefox." Unfortunately, that kind of makes it a non-starter for sooo many projects.
inaccessible rail said on mastodon.me.uk:
@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
Gustav Lindqvist 🇸🇪 said on jkpg.rocks:
@Edent once more browsers support :has this will be a very cool thing!
Jeff Sikes says:
@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/
Denilson Sá Maia says:
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.