I like my environments consistently themed. If an application does not support theming, chances are that I'm not going to use it. Recently, I've also been on a journey to have consistent dark/light mode toggling for all my apps. After switching from foot to alacritty (which I put off for a looooong time to avoid dealing with nixGL), Firefox was the last eyesore remaining.
Up until now, I was very happy with color.firefox.com for my theming needs. I've used a custom userChrome.css
before but got tired of updates breaking the style.
The only downside is the fact that the color addon does not support dark/light mode. What a shame!
In general, documentation around dynamic dark/light themes is very sparse which is why I'm writing this down.
Creating a custom theme
As previously mentioned, my starting point was the color addon. Taking a closer look at its functionality, an export button piqued my interest. It offers exports in the xpi
and zip
formats. In the end, both are zip files containing a manifest.json
document.
Taking a closer look, this is actually pretty readable:
{
"manifest_version": 2,
"version": "1.0",
"name": "test",
"theme": {
"colors": {
"toolbar": "rgb(43, 206, 227)",
"toolbar_text": "rgb(215, 226, 239)",
"frame": "rgb(115, 214, 228)",
"tab_background_text": "rgb(84, 84, 84)",
"toolbar_field": "rgba(255, 255, 255, 0.53)",
"toolbar_field_text": "rgb(255, 71, 203)",
"tab_line": "rgb(255, 66, 205)",
"popup": "rgb(231, 242, 244)",
"popup_text": "rgb(84, 84, 84)",
"tab_loading": "rgb(255, 66, 205)"
}
}
}
On its own, this doesnt get you very far. You now just have a differernt representation of the data you already had in the addon configuration. But one thing that stands out is the way the color codes are specified.
And a look at the documentation confirms this:
All these properties can be specified as either a string containing any valid CSS color string (including hexadecimal), or an RGB array, such as
"tab_background_text": [ 107 , 99 , 23 ]
.
As luck would have it, CSS supports a way to dynamicaly set a value based on the system preference.
light-dark()
is a CSS function that returns the first argument for light mode and the second for dark mode.
Using this to write the theme isn't very pretty but it should work:
{
"manifest_version": 2,
"version": "1.0",
"name": "test",
"theme": {
"colors": {
"toolbar": "light-dark(white,black)",
"toolbar_text": "light-dark(black,white)",
"frame": "light-dark(red,blue)",
"tab_background_text": "light-dark(yellow,green)",
"toolbar_field": "light-dark(white,black)",
"toolbar_field_text": "light-dark(black,white)",
"tab_line": "light-dark(blue,pink)",
"popup": "#aaaaaa",
"popup_text": "black",
"tab_loading": "#bada55"
}
}
}
To load this, create a zip file containing just this file and load it as a temporary add-on.
Great success - the values for our current scheme are loaded! Let's try switching theme…
Nothing happens? The glorious red of light-mode should switch to blue when the theme is switched so what is happening?
Grepping the documenation for dark
or light
brings up another property called color_scheme
. Setting this to system
instead of auto
does the trick!
{
"manifest_version": 2,
"version": "1.0",
"name": "test",
"theme": {
"properties": {
"color_scheme": "system"
},
"colors": {
"toolbar": "light-dark(white,black)",
"toolbar_text": "light-dark(black,white)",
"frame": "light-dark(red,blue)",
"tab_background_text": "light-dark(yellow,green)",
"toolbar_field": "light-dark(white,black)",
"toolbar_field_text": "light-dark(black,white)",
"tab_line": "light-dark(blue,pink)",
"popup": "#aaaaaa",
"popup_text": "black",
"tab_loading": "#bada55"
}
}
}
It works! With a bit more effort on the color selection, this can even look great!
Keeping the theme
As the name Load Temporary Add-on implies, the zip file you just loaded will be gone on the next Firefox restart. So how can you keep your new theme loaded? By signing it!
Sadly, Firefox does not allow for unsigned or arbitrarily signed plugins to be loaded so you need to go through the plugin signing process of the Firefox marketplace. Don't worry - you won't actually need to make your theme publicly available and manage store listings. The plugin can remain unlisted - just for your enjoyment.
Sign up for an account on the Mozilla Developer Hub and create a new API Key. Next, run the following command to submit your plugin:
npx web-ext sign \
--api-key=$YOUR_API_KEY \
--api-secret=$YOUR_API_SECRET \
-s . --channel=unlisted
This will also present you with a unique plugin ID to add to your manifest later on. The plugin is now submitted to Mozilla and all that's left for you is to wait. For simple themes like this, no human is going to look at your theme and it'll probably be automatically accepted after around 30 minutes.
Once that's done, you can install your new theme through the developer hub and it'll live across restarts 🥳.
Thunderbird bonus round
This being a web extension based plugin should mean that you can just reuse this in thunderbird right?
WRONG! Thunderbird does not know about the system
value for color_scheme
. But there is a way around this!
While light-dark
approach doesn't work, the Thunderbird manifest documentation mentions a top level field called dark_theme
. And as you might expect, this allows you to define a separate theme for dark mode.
{
"manifest_version": 2,
"version": "1.0",
"name": "test",
"theme": {
"images": {},
"properties": {
"color_scheme": "light"
},
"colors": {
"toolbar": "#fff6d8",
"toolbar_text": "#484431",
"frame": "#f5e9cb",
"tab_background_text": "#484431",
"toolbar_field": "#fff6d8",
"toolbar_field_text": "#484431",
"tab_line": "#68708a",
"popup": "#e7d7c6",
"popup_text": "#80431a",
"tab_loading": "#ba2d2f"
}
},
"dark_theme": {
"images": {},
"properties": {
"color_scheme": "dark"
},
"colors": {
"toolbar": "rgb(0, 0, 0)",
"toolbar_text": "rgb(254, 172, 208)",
"frame": "rgb(0, 0, 0)",
"tab_background_text": "rgb(191, 191, 191)",
"toolbar_field": "rgb(0, 0, 0)",
"toolbar_field_text": "rgb(191, 191, 191)",
"tab_line": "rgb(254, 172, 208)",
"popup": "rgb(0, 0, 0)",
"popup_text": "rgb(255, 255, 255)",
"tab_loading": "rgb(254, 172, 208)"
}
}
}
Thunderbird also adds some morecolor fields you can customize but to be honest: that's something for future Dominik to do.
Testing works the same way and it turns out security doesn't seem to be as much of a concern in Thunderbird world. Here you can just load the zip file as a regular addon!
It also turns out that Firefox uses dark_theme
as well but this is not documented so I'll stick to light-dark
for now.
Conclusion
- use
light-dark
in Firefox - you'll have to sign the plugin for it to stick around
- use
dark_theme
in Thunderbird
At some point I'll probably build a small tool to simplify working with this mechanism but for now you can find my themes in this repository. Happy hacking!