Welcome to this detailed guide showing you how to set up an effective Dark/Light-Mode switcher for your Arch Linux system with i3wm. In a world where most applications have their own theming logic, we will build an orchestration solution that controls many components simultaneously to create a consistent experience.

We will integrate GTK, Qt, terminal, and even CLI applications into our theme switcher, all controlled by a single keypress.

Changelog

DateChange
2026-05-04Initial Version: Comprehensive guide for Dark/Light-Mode switching on Arch Linux and i3wm.

Architectural Overview

i3wm itself does not provide global theme management. Styling is handled by various components such as GTK, Qt, Xresources, and individual applications. Our solution is based on an orchestrator architecture that uses darkman as the central control unit.

darkman performs two main tasks:

  1. It executes your customized hook scripts, which adapt specific applications and environments.
  2. It propagates the dark mode status via the XDG-Desktop-Portal to modern applications (e.g., Firefox, Thunderbird), which then switch their theme without needing a restart.
$mod+Shift+d  โ†’  darkman toggle  โ†’  Hook Scripts  โ†’  Individual Apps
                       โ”‚
                       โ””โ”€โ”€โ–บ XDG-Portal (Firefox, Thunderbird, etc. via Signal)

Quickstart (Copy & Paste)

If you just want a minimal, working setup โ€” GTK + Firefox + i3 + Alacritty controlled by $mod+Shift+d โ€” follow this Quickstart. You can extend with Polybar, VSCodium, neomutt, and Qt apps later by following the full guide below.

# 1. Install prerequisites
sudo pacman -S darkman xdg-desktop-portal xdg-desktop-portal-gtk \
               jq libnotify alacritty
yay -S dracula-gtk-theme   # or fall back to Adwaita-dark (handled by hook)

# 2. Create directory layout
mkdir -p ~/.local/share/dark-mode.d \
         ~/.local/share/light-mode.d \
         ~/.config/themes/{dracula,solarized-light} \
         ~/.config/alacritty \
         ~/.config/xdg-desktop-portal

# 3. Tell xdg-desktop-portal to use darkman as the Settings backend.
#    REQUIRED since xdg-desktop-portal 1.17.0 โ€” without this, Firefox &
#    other portal-aware apps will not see darkman's color-scheme.
cat > ~/.config/xdg-desktop-portal/portals.conf <<'EOF'
[preferred]
org.freedesktop.impl.portal.Settings=darkman
EOF

# 4. Pull terminal theme files
curl -fsSL https://raw.githubusercontent.com/dracula/alacritty/master/dracula.toml \
     -o ~/.config/themes/dracula/alacritty.toml
curl -fsSL https://raw.githubusercontent.com/alacritty/alacritty-theme/master/themes/solarized_light.toml \
     -o ~/.config/themes/solarized-light/alacritty.toml

# 5. Wire up i3 (idempotent โ€” safe to run twice)
grep -q "bindsym \$mod+Shift+d exec --no-startup-id darkman toggle" ~/.config/i3/config \
    || echo 'bindsym $mod+Shift+d exec --no-startup-id darkman toggle' >> ~/.config/i3/config
grep -q "include ~/.config/i3/theme.conf" ~/.config/i3/config \
    || echo 'include ~/.config/i3/theme.conf' >> ~/.config/i3/config

# 6. Enable the service
systemctl --user enable --now darkman.service

# 7. Quick smoke test
#    Note: these only have a *visible* effect after you create the
#    hook scripts in Steps 7 & 8. Until then darkman just flips an
#    internal flag โ€” nothing recolors yet.
darkman get      # current mode
darkman toggle   # switch without using the keybinding

Then create the i3 theme stubs and the hook scripts as described in Step 7 and Step 8. After that, $mod+Shift+d should already give you a working basic switch.

Expected result after the Quickstart + minimal hooks: Pressing $mod+Shift+d (or running darkman toggle) should immediately recolor i3 borders, switch the Alacritty theme without restarting the terminal, and โ€” if Firefox is open with widget.use-xdg-desktop-portal.settings = 1 set in about:config โ€” flip Firefoxโ€™s UI between light and dark. If any of these donโ€™t react, see the Reality Check section.

To avoid frustration and facilitate troubleshooting, you should approach the setup in phases. This way, youโ€™ll always know which component is responsible for problems.

  1. Phase 1 โ€” Basic: darkman + GTK + Firefox/Thunderbird + i3 + Alacritty. These components are reliable and cover the majority of the visual interface.
  2. Phase 2 โ€” Extension: Polybar + VSCodium + neomutt. This requires a bit more configuration, but the functionality is stable.
  3. Phase 3 โ€” Problem Children: Qt apps (KeePassXC, nheko), Chromium, urxvt live-reload. These applications can present app-specific challenges and should be tested separately.

Follow this tutorial linearly and test the functionality after each phase before moving on to the next.

Theme Choice: Dracula โ†” Solarized Light

For dark mode, we will use Dracula, a popular and well-supported theme. For the light mode counterpart, several options are available:

  • Solarized Light: A classic with well-documented color palettes for many tools, known for its good readability.
  • Catppuccin Latte: A modern alternative that aesthetically complements Catppuccin Mocha (if you decide to replace Dracula later).
  • GitHub Light: A pragmatic choice, as many editor themes and browser extensions are available for it.

This guide will use the Dracula โ†” Solarized Light pair.

Migrating from a hand-themed setup

If youโ€™ve already painstakingly themed each application by hand, read this section before running any of the hook scripts. The hooks are designed for a setup built with darkman in mind โ€” applied to an existing setup, they will overwrite or symlink-replace several files without asking.

What the hooks will overwrite (destructive)

These files are rewritten in full on every toggle:

  • ~/.config/gtk-3.0/settings.ini and ~/.config/gtk-4.0/settings.ini โ€” replaced via cat > ... <<EOF. Any custom keys you have here (cursor theme, font name, dconf-style overrides) will be lost on first toggle.
  • ~/.config/qt5ct/qt5ct.conf, ~/.config/qt6ct/qt6ct.conf โ€” only the color_scheme_path line is rewritten via sed. The rest of the file is preserved.
  • ~/.config/VSCodium/User/settings.json โ€” only the workbench.colorTheme key is updated via jq. Caveat: if your settings.json contains JSONC-style comments, jq will strip them.

These paths become symlinks pointing into ~/.config/themes/<theme>/. If a real file exists at the path, the symlink replaces it (the original file stays where it was, but is no longer reachable through the original path):

  • ~/.Xresources
  • ~/.config/i3/theme.conf
  • ~/.config/alacritty/theme.toml
  • ~/.config/polybar/config.ini
  • ~/.config/neomutt/colors
  • ~/.config/nvim/colorscheme.vim

What is changed system-wide via gsettings

org.gnome.desktop.interface gtk-theme and color-scheme โ€” affects every GTK app on your system that reads gsettings, regardless of whether you intended to theme it through darkman.

What is NOT touched

Your main configuration files (~/.config/i3/config, ~/.config/alacritty/alacritty.toml, your Polybar bar layout, your .muttrc, your Neovim init, your shell rc files, Firefox/Chromium profiles) are only referenced via include/import/source lines. They stay where they are.

mkdir -p ~/theme-switcher-backup
for path in \
    ~/.config/gtk-3.0 \
    ~/.config/gtk-4.0 \
    ~/.config/qt5ct \
    ~/.config/qt6ct \
    ~/.Xresources \
    ~/.config/VSCodium/User/settings.json
do
    [[ -e "$path" ]] && cp -a "$path" ~/theme-switcher-backup/
done

After the first toggle, diff what changed:

diff -r ~/theme-switcher-backup/gtk-3.0 ~/.config/gtk-3.0 || true

If you find that lines you cared about were lost, the right fix is not to protect your old config but to teach the hooks about your custom keys โ€” open the hook scripts in Step 7 and Step 8 and add your custom lines into the cat > settings.ini <<EOF blocks. Same logic applies to qt5ct, VSCodium, etc.: extend the hook, donโ€™t fight it.

Step 1: Install Prerequisites

darkman and Portal Infrastructure

sudo pacman -S darkman xdg-desktop-portal xdg-desktop-portal-gtk jq libnotify
systemctl --user enable --now darkman.service
  • jq is needed for patching VSCodium settings.
  • libnotify enables notifications via notify-send.

Configure xdg-desktop-portal to use darkman (REQUIRED)

This is the single most overlooked step in dark-mode setups. Since xdg-desktop-portal 1.17.0, the portal must be told which backend implements the Settings interface โ€” otherwise Firefox, Thunderbird, and other portal-aware apps will never see darkmanโ€™s color-scheme value, no matter how correctly darkman itself is configured.

Create ~/.config/xdg-desktop-portal/portals.conf:

mkdir -p ~/.config/xdg-desktop-portal
cat > ~/.config/xdg-desktop-portal/portals.conf <<'EOF'
[preferred]
org.freedesktop.impl.portal.Settings=darkman
EOF

You can verify the portal really hands darkmanโ€™s value to clients with:

gdbus call --session \
  --dest org.freedesktop.portal.Desktop \
  --object-path /org/freedesktop/portal/desktop \
  --method org.freedesktop.portal.Settings.ReadOne \
  org.freedesktop.appearance color-scheme

This should return 1 (dark) or 2 (light) depending on darkmanโ€™s current mode. If it returns 0 (โ€œno preferenceโ€) or fails, your portals.conf isnโ€™t being read โ€” restart xdg-desktop-portal.service and check XDG_CURRENT_DESKTOP is set in your session environment.

Firefox-side counterpart: In about:config, also set widget.use-xdg-desktop-portal.settings = 1. Without both halves โ€” system-side portals.conf AND Firefox-side preference โ€” the bridge stays broken.

GTK Theme

Dracula is not in the official Arch repository. You will need an AUR helper like yay or paru:

yay -S dracula-gtk-theme
# or: paru -S dracula-gtk-theme

If you prefer not to use an AUR helper, the setup will automatically fall back to Adwaita-dark and Adwaita respectively (see hook script).

Qt Control (for Phase 3)

sudo pacman -S qt5ct qt6ct kvantum

i3 Keybinding

Add the following keybinding to your i3 configuration file ~/.config/i3/config:

bindsym $mod+Shift+d exec --no-startup-id darkman toggle

Step 2: Create Directory Structure

A clean directory structure is crucial for maintainability:

mkdir -p ~/.local/share/dark-mode.d
mkdir -p ~/.local/share/light-mode.d
mkdir -p ~/.config/themes/{dracula,solarized-light}
mkdir -p ~/.config/qt5ct/colors ~/.config/qt6ct/colors

In the ~/.config/themes/ directory, theme-specific files for each application will be stored. The hook scripts will then create symlinks to the currently desired theme files.

Step 3: Prepare Qt Apps (KeePassXC, nheko)

Qt applications do not automatically follow GTK configuration or the XDG-Portal. You must set the QT_QPA_PLATFORMTHEME environment variable in ~/.xprofile, as this file is reliably read by i3 at startup.

Pragmatic starting point โ€” begin with qt5ct alone:

export QT_QPA_PLATFORMTHEME=qt5ct

This setting usually works most reliably in practice and often affects Qt6 applications via a compatibility path.

If Qt6 apps ignore theming (e.g., KeePassXC remains unstyled), you can escalate to the following configuration:

export QT_QPA_PLATFORMTHEME=qt5ct:qt6ct

This combined syntax is officially supported from Qt 6.5, but its effectiveness can vary depending on the appโ€™s build and Qt version. Only escalate if qt5ct alone is insufficient.

Note on path: ~/.config/environment.d/ would be the โ€œmore modernโ€ way, but itโ€™s read by systemd --user and doesnโ€™t reliably reach the X process in every i3 session โ€“ especially not in startx setups without a display manager. .xprofile is the more robust choice for i3 + X11.

Last-resort fallback: per-app wrapper

If neither variable above gets a stubborn Qt6 app to pick up the theme, set the variable just for that one app:

QT_QPA_PLATFORMTHEME=qt6ct keepassxc

You can wrap this in a small shell script and place it in ~/.local/bin/, or make a desktop file with the env-var prepended in Exec=. Inelegant, but reliable when the global escalation chain fails.

Obtain Qt Color Schemes

Qt color schemes are stored as .conf files under ~/.config/qt5ct/colors/ and ~/.config/qt6ct/colors/. Do not store them under /usr/share/qt5ct/colors/, as these directories are reserved for system defaults and can be overwritten by Pacman updates.

For Dracula, an official Qt5ct color scheme exists in the Dracula project:

curl -fsSL https://raw.githubusercontent.com/dracula/qt5/master/Dracula.conf \
     -o ~/.config/qt5ct/colors/Dracula.conf
cp ~/.config/qt5ct/colors/Dracula.conf ~/.config/qt6ct/colors/Dracula.conf

For Solarized Light, no official Qt5ct color scheme exists. The most reliable approach is to create one once via the GUI:

  1. Run qt5ct from a terminal.
  2. Tab โ€œStyleโ€ โ†’ next to โ€œColor schemeโ€ click โ€œEdit color schemeโ€ โ†’ โ€œCreateโ€.
  3. Set Window Background #fdf6e3, Window Text #586e75, Base #fdf6e3, Highlight #268bd2, etc. โ€“ the eight Solarized base values are documented at ethanschoonover.com/solarized.
  4. Save as SolarizedLight.conf under ~/.config/qt5ct/colors/.
  5. Copy the file to ~/.config/qt6ct/colors/SolarizedLight.conf.

Why not use a third-party Solarized Qt scheme? Various community ports exist, but their quality and maintenance vary, and the qt5ct .conf format is fairly strict. A one-time GUI creation gives you a result that exactly matches your other Solarized apps, without surprises.

Step 4: Split i3 Config

The i3 configuration cannot be recolored at runtime. You must switch it and reload.

Add the following line to ~/.config/i3/config:

include ~/.config/i3/theme.conf

Then create two theme-specific configuration files. Minimal working examples:

# Dracula i3 theme
cat > ~/.config/themes/dracula/i3-theme.conf <<'EOF'
# class                 border  bground text    indicator child_border
client.focused          #6272a4 #44475a #f8f8f2 #bd93f9   #6272a4
client.focused_inactive #44475a #44475a #f8f8f2 #44475a   #44475a
client.unfocused        #282a36 #282a36 #bfbfbf #282a36   #282a36
client.urgent           #44475a #ff5555 #f8f8f2 #ff5555   #ff5555
client.placeholder      #282a36 #282a36 #f8f8f2 #282a36   #282a36
client.background       #282a36
EOF

# Solarized Light i3 theme
cat > ~/.config/themes/solarized-light/i3-theme.conf <<'EOF'
# class                 border  bground text    indicator child_border
client.focused          #268bd2 #eee8d5 #586e75 #268bd2   #268bd2
client.focused_inactive #93a1a1 #eee8d5 #586e75 #93a1a1   #93a1a1
client.unfocused        #fdf6e3 #fdf6e3 #93a1a1 #fdf6e3   #fdf6e3
client.urgent           #93a1a1 #dc322f #fdf6e3 #dc322f   #dc322f
client.placeholder      #fdf6e3 #fdf6e3 #586e75 #fdf6e3   #fdf6e3
client.background       #fdf6e3
EOF

The hook script will then symlink the correct file to ~/.config/i3/theme.conf and execute i3-msg reload.

If you prefer not to use the include directive, you can symlink the entire i3 configuration file. This is less flexible but also works.

Step 5: Integrate Polybar

Polybar loads its configuration at startup. There are two ways to restart Polybar:

Option 1: polybar-msg cmd restart (clean, from Polybar 3.6)

polybar-msg cmd restart

Option 2: pkill + Launch Script (universal)

pkill polybar
sleep 0.3
~/.config/polybar/launch.sh

Minimal theme files

Youโ€™ll already have a working ~/.config/polybar/config.ini from your existing setup. The simplest approach is to extract just the colors into theme-specific files and include-file them. Minimal examples:

# Dracula Polybar palette
cat > ~/.config/themes/dracula/polybar.ini <<'EOF'
[colors]
background = #282a36
background-alt = #44475a
foreground = #f8f8f2
foreground-alt = #6272a4
primary = #bd93f9
secondary = #8be9fd
alert = #ff5555
disabled = #6272a4
EOF

# Solarized Light Polybar palette
cat > ~/.config/themes/solarized-light/polybar.ini <<'EOF'
[colors]
background = #fdf6e3
background-alt = #eee8d5
foreground = #586e75
foreground-alt = #93a1a1
primary = #268bd2
secondary = #2aa198
alert = #dc322f
disabled = #93a1a1
EOF

In your main Polybar config, reference the colors via include-file = ~/.config/polybar/colors.ini, and let the hook script symlink colors.ini to one of the two palette files. (If your existing config keeps colors and bar layout in a single file, store two complete copies in ~/.config/themes/<theme>/polybar.ini and let the hook symlink the whole config.ini instead โ€” both approaches work.)

Step 6: Chromium Flags

Chromium does not react to theme changes at runtime. Itโ€™s important to clearly separate two things:

  • UI-Dark-Mode: Affects the browser interface (toolbar, tabs, menus).
  • Webpage-Force-Dark: Forces a dark mode for every webpage. However, this can distort images, diagrams, and carefully designed pages.

Create the file ~/.config/chromium-flags.conf:

--force-dark-mode
--gtk-version=4

--gtk-version=4 ensures that Chromium respects the current GTK4 theme and colors its toolbar to match other GTK applications.

Optional: Additionally force dark content for webpages

If you want to force dark mode for webpage content as well, add:

--enable-features=WebContentsForceDark

Warning: This flag name has changed multiple times across Chromium versions (WebContentsForceDark, ForceWebContentsDarkMode, or only via chrome://flags). If it doesnโ€™t work, check chrome://flags for โ€œForce Dark Mode for Web Contentsโ€ and enable it there. The recommendation remains: only enable if youโ€™re comfortable with potential content distortion.

A true live toggle is not practical here without a browser restart.

Step 7: The Dark-Mode Hook Script

This script will be executed when darkman switches to dark mode. Create the file ~/.local/share/dark-mode.d/apply-theme:

A note on hook directory layout: This guide uses ~/.local/share/dark-mode.d/ and ~/.local/share/light-mode.d/, which the darkman(1) manpage describes as the legacy format (kept for backwards compatibility). The current format is a single ~/.local/share/darkman/ directory with one script that receives the mode as $1. Both formats work; the split-directory layout is used here because it makes the dark and light scripts easy to read side-by-side. If you want the modern single-script style, consolidate the two scripts and branch on case "$1" in dark|light) ... esac.

#!/usr/bin/env bash
# Robustness: undefined vars are errors, but individual app errors
# should not abort the entire hook.
set -u
trap 'echo "Hook error in: $BASH_COMMAND" >&2' ERR

THEME_DIR="$HOME/.config/themes/dracula"
GTK_THEME="Dracula"

# Fallback if dracula-gtk-theme is not installed
if ! [[ -d /usr/share/themes/Dracula || -d ~/.themes/Dracula ]]; then
    GTK_THEME="Adwaita-dark"
fi

# โ”€โ”€โ”€ GTK 3 / GTK 4 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
mkdir -p ~/.config/gtk-3.0 ~/.config/gtk-4.0
cat > ~/.config/gtk-3.0/settings.ini <<EOF
[Settings]
gtk-theme-name=$GTK_THEME
gtk-icon-theme-name=Adwaita
gtk-application-prefer-dark-theme=1
EOF
cp ~/.config/gtk-3.0/settings.ini ~/.config/gtk-4.0/settings.ini

# Also via gsettings (for apps that read this)
gsettings set org.gnome.desktop.interface gtk-theme "$GTK_THEME" 2>/dev/null || true
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' 2>/dev/null || true

# โ”€โ”€โ”€ Qt5 / Qt6 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Redirect ColorScheme path in qt5ct/qt6ct โ€” user config, not /usr/share!
if [[ -f ~/.config/qt5ct/qt5ct.conf ]]; then
    sed -i "s|^color_scheme_path=.*|color_scheme_path=$HOME/.config/qt5ct/colors/Dracula.conf|" \
        ~/.config/qt5ct/qt5ct.conf
fi
if [[ -f ~/.config/qt6ct/qt6ct.conf ]]; then
    sed -i "s|^color_scheme_path=.*|color_scheme_path=$HOME/.config/qt6ct/colors/Dracula.conf|" \
        ~/.config/qt6ct/qt6ct.conf
fi

# โ”€โ”€โ”€ Xresources / urxvt โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/Xresources" ~/.Xresources
xrdb -merge ~/.Xresources

# Recolor running urxvt instances via escape sequences (best effort).
# -O tests if the executing user is the owner of the PTS โ€” protects in
# multi-user/SSH setups from writing to foreign terminals.
# Note: These sequences only change Background/Foreground, not ANSI colors.
for pts in /dev/pts/[0-9]*; do
    if [[ -O "$pts" && -w "$pts" ]]; then
        printf '\033]708;#282a36\007\033]11;#282a36\007\033]10;#f8f8f2\007' \
            > "$pts" 2>/dev/null || true
    fi
done

# โ”€โ”€โ”€ Alacritty โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/alacritty.toml" ~/.config/alacritty/theme.toml
# Alacritty reloads automatically with live_config_reload=true

# โ”€โ”€โ”€ i3 Theme โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/i3-theme.conf" ~/.config/i3/theme.conf
i3-msg reload >/dev/null

# โ”€โ”€โ”€ Polybar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/polybar.ini" ~/.config/polybar/config.ini
if command -v polybar-msg >/dev/null 2>&1; then
    polybar-msg cmd restart >/dev/null 2>&1 || {
        pkill polybar; sleep 0.3; ~/.config/polybar/launch.sh &
    }
else
    pkill polybar; sleep 0.3; ~/.config/polybar/launch.sh &
fi

# โ”€โ”€โ”€ neomutt โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/neomutt-colors" ~/.config/neomutt/colors
# Running neomutt instances require :source ~/.config/neomutt/colors

# โ”€โ”€โ”€ VSCodium โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Patch settings.json. VSCodium usually reacts live via file watcher,
# in rare cases a "Reload Window" (Ctrl+Shift+P) is needed.
SETTINGS="$HOME/.config/VSCodium/User/settings.json"
if [[ -f "$SETTINGS" ]] && command -v jq >/dev/null 2>&1; then
    tmp=$(mktemp)
    jq '."workbench.colorTheme" = "Dracula"' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
fi

# โ”€โ”€โ”€ Vim/Neovim (if used) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/nvim-colorscheme.vim" ~/.config/nvim/colorscheme.vim 2>/dev/null || true

# โ”€โ”€โ”€ Firefox / Thunderbird โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Nothing to do โ€” they follow darkman's XDG-Portal automatically.
# Prerequisite: in about:config widget.use-xdg-desktop-portal.settings = 1

# โ”€โ”€โ”€ Chromium โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Does not react live; flags in ~/.config/chromium-flags.conf take effect
# on next start. See Step 6.

# โ”€โ”€โ”€ User Notification โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
notify-send -i weather-clear-night "Theme" "Dark Mode activated (Dracula)" 2>/dev/null || true
echo "Dark mode activated (Dracula)"

Make the script executable:

chmod +x ~/.local/share/dark-mode.d/apply-theme

Step 8: The Light-Mode Hook Script

This script will be executed when darkman switches to light mode. Create the file ~/.local/share/light-mode.d/apply-theme:

#!/usr/bin/env bash
set -u
trap 'echo "Hook error in: $BASH_COMMAND" >&2' ERR

THEME_DIR="$HOME/.config/themes/solarized-light"
GTK_THEME="Adwaita"

# โ”€โ”€โ”€ GTK 3 / GTK 4 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
mkdir -p ~/.config/gtk-3.0 ~/.config/gtk-4.0
cat > ~/.config/gtk-3.0/settings.ini <<EOF
[Settings]
gtk-theme-name=$GTK_THEME
gtk-icon-theme-name=Adwaita
gtk-application-prefer-dark-theme=0
EOF
cp ~/.config/gtk-3.0/settings.ini ~/.config/gtk-4.0/settings.ini

gsettings set org.gnome.desktop.interface gtk-theme "$GTK_THEME" 2>/dev/null || true
gsettings set org.gnome.desktop.interface color-scheme 'prefer-light' 2>/dev/null || true

# โ”€โ”€โ”€ Qt5 / Qt6 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if [[ -f ~/.config/qt5ct/qt5ct.conf ]]; then
    sed -i "s|^color_scheme_path=.*|color_scheme_path=$HOME/.config/qt5ct/colors/SolarizedLight.conf|" \
        ~/.config/qt5ct/qt5ct.conf
fi
if [[ -f ~/.config/qt6ct/qt6ct.conf ]]; then
    sed -i "s|^color_scheme_path=.*|color_scheme_path=$HOME/.config/qt6ct/colors/SolarizedLight.conf|" \
        ~/.config/qt6ct/qt6ct.conf
fi

# โ”€โ”€โ”€ Xresources / urxvt โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/Xresources" ~/.Xresources
xrdb -merge ~/.Xresources

for pts in /dev/pts/[0-9]*; do
    if [[ -O "$pts" && -w "$pts" ]]; then
        printf '\033]708;#fdf6e3\007\033]11;#fdf6e3\007\033]10;#586e75\007' \
            > "$pts" 2>/dev/null || true
    fi
done

# โ”€โ”€โ”€ Alacritty โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/alacritty.toml" ~/.config/alacritty/theme.toml

# โ”€โ”€โ”€ i3 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/i3-theme.conf" ~/.config/i3/theme.conf
i3-msg reload >/dev/null

# โ”€โ”€โ”€ Polybar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/polybar.ini" ~/.config/polybar/config.ini
if command -v polybar-msg >/dev/null 2>&1; then
    polybar-msg cmd restart >/dev/null 2>&1 || {
        pkill polybar; sleep 0.3; ~/.config/polybar/launch.sh &
    }
else
    pkill polybar; sleep 0.3; ~/.config/polybar/launch.sh &
fi

# โ”€โ”€โ”€ neomutt โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/neomutt-colors" ~/.config/neomutt/colors

# โ”€โ”€โ”€ VSCodium โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
SETTINGS="$HOME/.config/VSCodium/User/settings.json"
if [[ -f "$SETTINGS" ]] && command -v jq >/dev/null 2>&1; then
    tmp=$(mktemp)
    jq '."workbench.colorTheme" = "Solarized Light"' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
fi

# โ”€โ”€โ”€ Neovim โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
ln -sf "$THEME_DIR/nvim-colorscheme.vim" ~/.config/nvim/colorscheme.vim 2>/dev/null || true

# โ”€โ”€โ”€ User Notification โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
notify-send -i weather-clear "Theme" "Light Mode activated (Solarized Light)" 2>/dev/null || true
echo "Light mode activated (Solarized Light)"

Make the script executable:

chmod +x ~/.local/share/light-mode.d/apply-theme

Step 9: Initial State and Persistence on Login

darkman is designed as a user service that persists its own state. The service typically remembers the last mode after a restart and executes the corresponding hooks at startup.

Important โ€” no hardcoded darkman set calls in .xprofile! It might be tempting to write darkman set dark in .xprofile to โ€œalways start with Dark mode.โ€ However, this is counterproductive:

  • It overwrites the last manual toggle on every login.
  • It completely disables auto-switching based on sunrise/sunset, if you enable it later.

Just let the service run.

Reality Check: Verify Persistence

In practice, reliable persistence depends on the interplay between the user service, login manager, and XDG state directory. Do not blindly trust it โ€“ check it after the first login cycle:

# What does darkman currently think?
darkman get

# Was the service started cleanly at login and did it trigger hooks?
journalctl --user -u darkman.service -b

If darkman get returns a different mode than expected, or if the hook scripts were not executed at login, you have a persistence problem (see next section).

If Persistence is Unreliable

Some possible countermeasures:

  • Ensure darkman.service is truly enabled: systemctl --user is-enabled darkman.service
  • In rare setups (TTY login + startx, no display manager), it helps to set dbus-update-activation-environment --systemd DISPLAY XAUTHORITY in .xprofile so that user services can see the X session.
  • Workaround: In .xprofile, call darkman get once and only set a fallback mode if itโ€™s empty. However, this is a hack โ€“ itโ€™s better to address the root cause of the problem.

Optional: Auto-Switching Based on Sunrise/Sunset

If darkman should automatically switch based on time of day, create the file ~/.config/darkman/config.yaml:

lat: 48.2082
lng: 16.3738
usegeoclue: false

(The coordinates provided here are for Vienna โ€“ adjust them to your location.)

The service calculates sunrise and sunset times from this and automatically triggers the hooks.

If you really need a default value on the very first start

If youโ€™ve just installed darkman and want a defined initial state for the very first toggle, perform this manually once in the shell:

darkman set dark

โ€” not in .xprofile. From the next login onwards, the service will remember.

Step 10: Obtain Theme Files

For each application, place a dark and a light variant in ~/.config/themes/<theme>/. The following commands cover most cases. You can paste these directly:

Alacritty

curl -fsSL https://raw.githubusercontent.com/dracula/alacritty/master/dracula.toml \
     -o ~/.config/themes/dracula/alacritty.toml
curl -fsSL https://raw.githubusercontent.com/alacritty/alacritty-theme/master/themes/solarized_light.toml \
     -o ~/.config/themes/solarized-light/alacritty.toml

In your main ~/.config/alacritty/alacritty.toml, import the symlink the hook will manage and confirm live-reload is on. Live-reload defaults to enabled in current Alacritty versions, but being explicit avoids confusion if a future upstream change flips the default:

import = ["~/.config/alacritty/theme.toml"]

[general]
live_config_reload = true   # default; explicit for clarity

TOML note: Pre-0.13 Alacritty used a top-level live_config_reload = true. From 0.13 onward, it lives under [general]. If your Alacritty is older, drop the [general] table.

Xresources / urxvt

# Dracula
curl -fsSL https://raw.githubusercontent.com/dracula/xresources/master/Xresources \
     -o ~/.config/themes/dracula/Xresources

For Solarized Light, the upstream solarized/xresources file uses C preprocessor #define statements. Many display managers and login flows invoke xrdb with -nocpp, which silently strips these defines and leaves you with default colors. To avoid this entirely, write a flat (preprocessor-free) version directly:

cat > ~/.config/themes/solarized-light/Xresources <<'EOF'
! Solarized Light โ€” flat, no #define preprocessing required
*background:            #fdf6e3
*foreground:            #657b83
*fadeColor:             #fdf6e3
*cursorColor:           #586e75
*pointerColorBackground:#93a1a1
*pointerColorForeground:#586e75

! black
*color0:  #073642
*color8:  #002b36
! red
*color1:  #dc322f
*color9:  #cb4b16
! green
*color2:  #859900
*color10: #586e75
! yellow
*color3:  #b58900
*color11: #657b83
! blue
*color4:  #268bd2
*color12: #839496
! magenta
*color5:  #d33682
*color13: #6c71c4
! cyan
*color6:  #2aa198
*color14: #93a1a1
! white
*color7:  #eee8d5
*color15: #fdf6e3
EOF

The values above are taken from the canonical Solarized palette and match the structure of the upstream file with the #defines already resolved.

Polybar

See Step 5 โ€” minimal Dracula and Solarized Light palettes are inlined there. Copy them into ~/.config/themes/<theme>/polybar.ini if you havenโ€™t already.

neomutt

# Dracula
mkdir -p ~/.config/themes/dracula
curl -fsSL https://raw.githubusercontent.com/dracula/mutt/master/dracula.muttrc \
     -o ~/.config/themes/dracula/neomutt-colors

# Solarized Light (16-color version recommended for terminal accuracy)
curl -fsSL https://raw.githubusercontent.com/altercation/mutt-colors-solarized/master/mutt-colors-solarized-light-16.muttrc \
     -o ~/.config/themes/solarized-light/neomutt-colors

VSCodium

VSCodium (like VS Code) ships with Solarized Light and Solarized Dark as built-in themes โ€” no extension required. Dracula, however, must be installed separately:

codium --install-extension dracula-theme.theme-dracula

The hook scripts already set the correct theme name via jq. If your VSCodium uses a different binary name (e.g., vscodium), adjust the command accordingly.

GTK and Qt

GTK Dracula via AUR (yay -S dracula-gtk-theme); Adwaita is shipped by default. Qt color schemes: see Step 3.

Reference table

AppFileSource
GTKTheme via AUR/Pacmandracula-gtk-theme, Adwaita is default
Xresources/urxvtXresourcesdracula/xresources, solarized/xresources
Alacrittyalacritty.tomldracula/alacritty, alacritty/alacritty-theme
i3i3-theme.confinlined examples in Step 4
Polybarpolybar.iniinlined examples in Step 5
neomuttneomutt-colorsdracula/mutt, altercation/mutt-colors-solarized
VSCodiumbuilt-in / ExtensionSolarized built-in, โ€œDracula Officialโ€ via marketplace
qt5ct/qt6ct*.confdracula/qt5, Solarized via qt5ct GUI

You can find Dracula themes collected at https://draculatheme.com โ€“ Solarized at https://ethanschoonover.com/solarized.

Caveats and Honest Expectations

What switches live (without restart):

  • GTK apps via gsettings/portal: Thunar, Firefox, Thunderbird (with Portal setting)
  • Alacritty (with live_config_reload = true)
  • i3 (after reload)
  • Polybar (after restart)
  • VSCodium (mostly, via file watcher on settings.json)

What requires an app restart:

  • KeePassXC, nheko (Qt apps do not react live)
  • GIMP, LibreOffice (own theme logic)
  • urxvt terminals (except with escape sequence trick, which only affects Background/Foreground, not ANSI colors)
  • neomutt (or :source within the session)
  • Chromium (flags file takes effect on start)

What needs to be configured manually within the app:

  • LibreOffice: set Light/Dark once in Tools โ†’ Options โ†’ Application Colors
  • GIMP: has its own theme system under Preferences โ†’ Theme
  • Inkscape: follows GTK, but restart required

Tips

  • Geo-based Auto-Switching: darkman can do this automatically based on sunrise/sunset โ€“ see Step 9.
  • Debugging: journalctl --user -u darkman.service -f shows if hooks are being executed.
  • Manual Hook Testing: Execute ~/.local/share/dark-mode.d/apply-theme directly to see errors in isolation.
  • Consistency Across Multiple Machines: Version ~/.config/themes/, ~/.local/share/dark-mode.d/, ~/.local/share/light-mode.d/, and ~/.config/qt5ct/colors/ in your dotfiles repository.
  • Forced KeePassXC Reload (optional, usually undesirable): If you absolutely need the KeePassXC theme to update at the moment of switching, you can include pkill keepassxc in the hook โ€“ but remember that this will lose all unsaved changes and the open database. Realistically: just let it switch on the next start.

Extension Ideas

  • base16-Framework instead of Dracula/Solarized: Generate a unified scheme for hundreds of apps from a single palette (https://github.com/chriskempson/base16).
  • Per-Workspace-Themes: Theoretically possible via i3-IPC, but rarely practical.
  • Polybar Module with current theme status as an indicator.

Summary

The implemented architecture is:

darkman (Trigger + Portal + Persistence)
    โ”œโ”€โ”€ Hooks โ†’ GTK, Qt, Xresources, App Configs
    โ”œโ”€โ”€ Live-Reload: i3, Polybar, Alacritty, GTK Apps via Portal, VSCodium
    โ””โ”€โ”€ Accepted: Some apps require restart

You switch with $mod+Shift+d. Most apps react immediately, some require a restart โ€“ this is the current state of Linux desktop reality, not a flaw in your setup.

When setting up for the first time, itโ€™s worthwhile to follow the phased strategy from the beginning of the tutorial: first the basics (GTK, Firefox, i3, Alacritty), then extensions, and finally the โ€œproblem children.โ€ This way, you can more easily identify the cause of problems if they arise.

๐Ÿ“šARCH WIKI: DARK MODE THEME SWITCHING ๐Ÿ’ปDARKMAN PROJECT ON GITHUB