I recently build a small home-manager thing that allows me to easily toggle themes:
You’ll notice a few things on invocation:
-
The most obvious one is probably Firefox doing a live reload of everything, even webpages if they are configured to use the system theme.
-
But also my terminal-emulator (kitty) doing the same and even the zsh-prompts (powerlevel10k) from past commands.
-
Waybar doesn’t live reload but my implementation just calls a home-manager activation script and thus the user systemd unit for waybar gets restarted and waybar parses it’s config again.
-
The icon theme changes from light to dark and back.
While doing so I encountered a few problems, qt applications don’t have a life reload (at least qdmr that’s the one I tested it with). FZF gets it theme through a env var I set in the zshrc and that doesn’t get live reloaded and I didn’t really find a fancy way of doing that and lastly chromium/electron is just deciding to be as crappy as usual and also won’t do a live reload which doesn’t really bother me too much since the only chromium application I use is element-desktop.
Now let’s go to the interesting part of how I implemented it:
Using @Misterio 's nix-colors I specified the dracula base16 theme as my default in home-manager as you would with any other option like:
colorScheme = {
slug = "dracula";
colors = {
base00 = "#282A36";
base01 = "#3A3C4E";
base02 = "#44475A";
base03 = "#6272A4";
base04 = "#62D6E8";
base05 = "#F8F8F2";
base06 = "#F1F2F8";
base07 = "#80008E";
base08 = "#FF5555";
base09 = "#FFB86C";
base0A = "#F1FA8C";
base0B = "#50FA7B";
base0C = "#8BE9FD";
base0D = "#BD93F9";
base0E = "#FF79C6";
base0F = "#00F769";
};
};
and the used the values in other configs like the one for my notification daemon
{ config, ... }:
{
services.mako = {
enable = true;
defaultTimeout = 10000;
extraConfig = ''
ignore-timeout=1
anchor=bottom-right
background-color=#${config.colorScheme.colors.base00}
text-color=#${config.colorScheme.colors.base05}
border-color=#${config.colorScheme.colors.base00}
[urgency=low]
border-color=#${config.colorScheme.colors.base00}
[urgency=normal]
border-color=#${config.colorScheme.colors.base0A}
[urgency=high]
border-color=#${config.colorScheme.colors.base08}
'';
};
}
To get the toggling capability I just defined something called a specialisation, you should note here that home-manager considers specialisation as experimental
specialisation.light-theme.configuration = {
# We have to force the values below to override the ones defined above
colorScheme = lib.mkForce {
slug = "catppuccinLatte";
colors = {
base00 = "#eff1f5";
base01 = "#e6e9ef";
base02 = "#ccd0da";
base03 = "#bcc0cc";
base04 = "#acb0be";
base05 = "#4c4f69";
base06 = "#dc8a78";
base07 = "#7287fd";
base08 = "#d20f39";
base09 = "#fe640b";
base0A = "#df8e1d";
base0B = "#40a02b";
base0C = "#179299";
base0D = "#1e66f5";
base0E = "#8839ef";
base0F = "#dd7878";
};
};
home.packages = with pkgs; [
# note the hiPrio which makes this script more important then others and is usually used in nix to resolve name conflicts
(hiPrio (writeShellApplication {
name = "toggle-theme";
runtimeInputs = with pkgs; [ home-manager coreutils ripgrep ];
# the interesting part about the script below is that we go back two generations
# since every time we invoke a activation script home-manager creates a new generation
text =
''
"$(home-manager generations | head -2 | tail -1 | rg -o '/[^ ]*')"/activate
'';
}))
];
};
Other then that I also have a shorthand to activate the specialization:
home.packages = with pkgs; [
(writeShellApplication {
name = "toggle-theme";
runtimeInputs = with pkgs; [ home-manager coreutils ripgrep ];
text =
''
"$(home-manager generations | head -1 | rg -o '/[^ ]*')"/specialisation/light-theme/activate
'';
})
];
And in some places where color pattersn just don’t match or stuff like the gtk and icon theme I initially used if statements but then changed to “switch cases” after @ajs124 showed me them because they have a nicer syntax in this case:
gtk = {
enable = true;
theme = {
dracula = {
name = "Dracula";
package = pkgs.dracula-theme;
};
catppuccinLatte = {
name = "Catppuccin-Latte-Compact-Lavender-light";
package = pkgs.catppuccin-gtk.override {
accents = ["lavender"];
size = "compact";
variant = "latte";
};
};
}.${config.colorScheme.slug};
iconTheme = {
package = pkgs.papirus-icon-theme;
name = {
dracula = "Papirus-Dark";
catppuccinLatte = "Papirus-Light";
}.${config.colorScheme.slug};
};
};
One last thing to note is that you have to set:
home-manager.useUserPackages = false;
in the NixOs module options otherwise the hiPrio thing above won’t work, thank you to @maralorn for helping me debug this, it was quite hard to find.
A disadvantage of this approach is that you basically double your home-manager evaluation time because everything gets evaluate twice (but the activation script finishes in less then a second)
And a advantage of this is that it does everything home-manager would usually do when doing a rebuild in terms of calling activation hooks and it feels quite native to integrate it in to a nix config.
@lassulus showed me his way of doing this which is:
Feel free to ask questions and give feedback, this is the first time where I write about something more implementation focused