With the recent public release of macOS Mojave and as a developer, I’m sure you’ve already got a ton of requests to make a dark theme version of your app. We sure did.
We kept postponing development because of a major technical roadblock: We were using SASS variables and when it comes down to it, these are pretty much static. Or at least they are once compiled into CSS. We also didn’t want our CSS to get any bigger than it is or even deal with a different file per theme. We’re eventually looking for 100% customizable themes.
But I have good news for you, CSS Variables show a whopping 86.8% support rate. For Electron apps, that’s not even a concern 💯.
Goal: 3 themes with minimal effort
light, dark and light-dark-sidebar are the initial themes we’re looking for. The beauty of the technique explained below is that we only need 2 base themes, the 3rd one being a mix of both and only requires one extra CSS selector.
By minimal effort, not only do I mean being able to add new themes only by editing existing CSS Variables, but also from a performance perspective. The HTML is to not be made aware of the current theme and no re-rendering of any sort is to be required when changing theme.
Bonus: Quite easy to quickly debug when all you have to do is change an attribute on
<html> via the web console 🐛.
We don’t want no FOUC!
What’s worse than seeing a light theme appear for a few milliseconds then switch to a dark one? Nothing 👏🏼 is 👏🏼 worse.
To avoid FOUC, we’ll use
localStorage and a preloaded script on
BrowserWindow. The preloaded script is loaded as soon as your app is launched and while
<html> can’t be accessed yet,
systemPreferences API provided by Electron, we know if the OS is currently using dark mode and can subscribe to the theme change event.
Then we’ll define the
__setTheme function in
index.html and make sure it’s called when launching the app. On the first run, the preloaded script already set
localStorage.os_theme, so we can use that and set an attribute on
<html> as soon as possible, especially before loading the styles. We’ll also preemptively read
localStorage.user_theme in case you want to let users choose their own theme and not rely on the OS only.
Extra specificity selector
That’s a way to make themes embeddable within each other. You could force any part of your app to have a given theme by setting the
Nota bene: Without that, even if you add
data-theme="light" anywhere in your HTML, it would still use the
<html data-theme="dark"> variables. Because for the same specificity, dark variables are being declared after in the CSS. That’s just good old cascading rules being applied.
That is so both
light-dark-sidebar use the same base theme.
Scoping variables with
That is to have theme variants without having to make the HTML aware of it. For that theme, the light theme is used, but the
<nav> element is going to use the dark variables. That way, you don’t have to re-render any part of your HTML when changing the theme, it’s just a matter of updating the
<html data-theme> and Voila!
Don’t bother with transitions, even if that’s just CSS it’s 100% seamless on Mojave. macOS freezes the current frame and renders the new layout behind the scenes. When ready, it’ll crossfade the two layouts making your app look as native as Finder.