Dynamic theme change in Kitty on Ubuntu

Dawid Laszuk published on
4 min, 650 words

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:

  1. GNOME exposes the current color scheme through org.gnome.desktop.interface color-scheme.
  2. Kitty loads its colors from an included file: current-theme.conf.
  3. A script switches that file between a light and dark theme.
  4. The same script sends kitty @ set-colors to 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,
  • systemd keeping the watcher alive.

That is enough.

The practical lesson

There are really two separate problems here:

  1. making future Kitty windows start with the right theme,
  2. 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.