Dynamic theme change in Kitty on Ubuntu
Kitty does not automatically follow the Ubuntu desktop light/dark theme in the way I wanted. New windows can pick up config changes, but existing windows will happily stay wrong unless you push new colors into the running instance.
So I set up a native GNOME-based solution with no extra theme daemon. Just gsettings, a small shell script, and a user systemd service.
The idea
The setup has four moving parts:
- GNOME exposes the current color scheme through
org.gnome.desktop.interface color-scheme. - Kitty loads its colors from an included file:
current-theme.conf. - A script switches that file between a light and dark theme.
- The same script sends
kitty @ set-colorsto running Kitty instances so existing windows update immediately.
That last part matters. Without it, only newly opened Kitty windows get the new theme.
Kitty configuration
In ~/.config/kitty/kitty.conf I added:
listen_on unix:/run/user/1000/kitty-theme-sync.sock
include current-theme.conf
allow_remote_control yes
This does three things:
- enables remote control,
- makes Kitty listen on a Unix socket,
- and loads colors from a separate file that can be swapped dynamically.
Separate light and dark theme files
I created two files:
~/.config/kitty/theme-dark.conf~/.config/kitty/theme-light.conf
Then current-theme.conf becomes a symlink to one of them.
Example dark theme:
background #1e1e2e
foreground #cdd6f4
selection_background #585b70
selection_foreground #cdd6f4
cursor #f5e0dc
cursor_text_color #1e1e2e
active_border_color #89b4fa
inactive_border_color #6c7086
bell_border_color #f38ba8
url_color #89b4fa
Example light theme:
background #eff1f5
foreground #4c4f69
selection_background #ccd0da
selection_foreground #4c4f69
cursor #dc8a78
cursor_text_color #eff1f5
active_border_color #1e66f5
inactive_border_color #9ca0b0
bell_border_color #d20f39
url_color #1e66f5
Sync script
The core script is:
#!/usr/bin/env bash
set -euo pipefail
KITTY_DIR="$HOME/.config/kitty"
SOCKET_GLOB="/run/user/$(id -u)/kitty-theme-sync.sock*"
SCHEME="$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null || echo "'default'")"
if [[ "$SCHEME" == *"dark"* ]]; then
THEME="$KITTY_DIR/theme-dark.conf"
else
THEME="$KITTY_DIR/theme-light.conf"
fi
ln -sf "$THEME" "$KITTY_DIR/current-theme.conf"
shopt -s nullglob
for socket in $SOCKET_GLOB; do
kitty @ --to "unix:$socket" set-colors -a "$THEME" >/dev/null 2>&1 || true
done
A small but important detail: I first assumed the socket would exist exactly at:
/run/user/1000/kitty-theme-sync.sock
That was wrong.
Kitty created instance-specific sockets like:
/run/user/1000/kitty-theme-sync.sock-130320
So the fix was to target kitty-theme-sync.sock* and update every live Kitty socket.
That was the difference between:
- title bar colors changing but terminal content staying stale,
- and full live theme switching actually working.
Watching GNOME for changes
The watcher script is simple:
#!/usr/bin/env bash
set -euo pipefail
"$HOME/.local/bin/kitty-sync-theme"
gsettings monitor org.gnome.desktop.interface color-scheme | while read -r _; do
"$HOME/.local/bin/kitty-sync-theme"
done
gsettings monitor blocks and emits a line every time the GNOME color scheme changes. That is enough.
Running it automatically
I run the watcher as a user service:
[Unit]
Description=Sync Kitty theme with GNOME light/dark mode
After=graphical-session.target
PartOf=graphical-session.target
[Service]
ExecStart=%h/.local/bin/kitty-watch-gnome-theme
Restart=always
RestartSec=1
[Install]
WantedBy=default.target
Then:
systemctl --user daemon-reload
systemctl --user enable --now kitty-theme-sync.service
Why this setup is decent
It is native to Ubuntu GNOME, minimal, and boring in a good way.
No extra background tool. No desktop-agnostic abstraction layer. No polling loop.
Just:
- GNOME exposing current theme state,
- Kitty exposing remote control,
systemdkeeping the watcher alive.
That is enough.
The practical lesson
There are really two separate problems here:
- making future Kitty windows start with the right theme,
- making already-running Kitty windows update in place.
The include file solves the first one.
The remote socket plus kitty @ set-colors solves the second.
Miss either part and the experience feels half-broken.
If you are on Ubuntu GNOME and want Kitty to follow desktop light/dark mode properly, this approach works.