Light or dark?
Posted 2022-05-16 22:11. Tagged web, meta, javascript, css, r4s.
I’ve had a light and a dark theme on this site since I switch from python
to rust.
But until now I have only used css @media
selection to enable the
dark theme, so it hasn’t been very discoverable.
If you have a browser that supports the prefers-color-scheme
query and
you have found that setting and enabled dark mode, you have seen this site
in the dark theme (and may not know that it had a light theme), otherwise you have
seen the site in the light theme (and not known about the dark).
So now this site have a “theme” button in the header. By clicking it you toggle between the “device default”, “dark” and “light” theme. The default is the “device default”, which is the same as before I introduced the button, either dark or light based on browser preference. So people who have made an active choise in their browser get that choise on this site automatically.
So, how do I implement this? The changes is a PR for kaj/r4s on github. What follows in this post is a walk-through of the main parts.
The old way (well, what I did from january to may 2022) was to have css code like this:
}
{
}
}
As far as I know, it’s not possible to change the prefers-color-scheme
preference from a button in a web page.
And anyway, I’d like to make it possible to switch in browsers that don’t
have that preference.
What I can easily do from a button is change a class attribute on the html
element.
So what I need is this:
/* All common and light-theme css */
}
{
}
}
Typing all the dark theme overrides twice is not really a problem, since I use sass, so the real code looks like this:
@mixin darktheme {
/* All dark theme overrides */
}
html.theme-dark { @include darktheme; }
@media (prefers-color-scheme: dark) {
html:not(.theme-light) { @include darktheme; }
}
But it makes the resulting css longer than necessary. And maintaining the dark mode overrides when I change anything in the css is harder than it should be. Surely there must be a better way?
Yes, there is. Css variables has been around since 2017, and by now they are supported by pretty much all browsers except discontinued MSIE and Opera Mini. So instead of trying to find all the styles I need to override to make a dark theme, I can move all colors to css variables and only override the variable declarations for the dark theme.
Like this:
}
@}
}
{
}
}
}
/* ... and all the rest of the styling */
That’s all for the css. What remains is the javascript for the button:
let b = document.;
let t = localStorage.;
This has two pieces; a small one that is executed as soon as possible, and a
larger (the init
function) that is executed after the full DOM for the web
page is loaded.
The immediate piece checks local storage for a field name theme
, and if
there is one it sets the relevant theme-
class on the root element (<html>
).
The init
function creates a <button>
(with the text theme
or tema
depending on the language) and sets an event listener for it.
The listener cycles through the theme-dark
, theme-light
and
theme-default
classes for the root element, and saves the current value to
local storage (for when the user goes to another page on the site, or goes
away and comes back later).
The classList.replace
function was really usefull here.
If the classList contains the first token, it replaces it with the second
and returns true, otherwise it returns false, keeping the check and the
replace atomic, reducing the risk of bugs.
In the third case, i don’t use that replace
function, since not having any
recognized theme- class is considered equivalent to having a theme-default
class.
So, what do you think? Is there any obvious better way to do this that I missed? Do the themes look reasonably well on your device? Do you prefer the dark or the light theme? Comments are welcome!
Comments
Write a comment