Skip to content

Auto light/dark theme switching#296

Open
djensenius wants to merge 33 commits intomainfrom
auto-theme-switching
Open

Auto light/dark theme switching#296
djensenius wants to merge 33 commits intomainfrom
auto-theme-switching

Conversation

@djensenius
Copy link
Copy Markdown
Owner

@djensenius djensenius commented Feb 27, 2026

Summary

Automatically switches all tool themes between Catppuccin Latte (light) and Catppuccin Mocha (dark) based on macOS system appearance. On non-macOS systems, uses sunrise/sunset calculations from ~/.config/tz.conf.

What switches

Instant (native support): Ghostty, Neovim (via catppuccin flavour = "auto")

Via switch script (sed-based): starship, gitconfig (delta), atuin, zellij, btop, k9s, tmux (catppuccin plugin)

Via switch script (symlinks): bottom, eza, yazi, gh-dash

Via fish universal variable: fish syntax highlighting colors, BAT_THEME, FZF_DEFAULT_OPTS, DARK_MODE, COLORFGBG

Architecture

  • scripts/theme-watcher.swift — macOS daemon that polls AppleInterfaceStyle every 2 seconds
  • scripts/switch-theme.fish — Central orchestrator that updates all configs
  • scripts/detect-theme-mode — POSIX shell detection (macOS / tz.conf sunrise-sunset / fallback)
  • fish/conf.d/99-theme.fish — Fish event handler for instant propagation via universal variable
  • scripts/sunrise-sunset.py — Pure Python NOAA algorithm (no pip deps) for non-macOS

Notes

  • Files modified by the theme switcher at runtime use git update-index --skip-worktree to keep git clean
  • Each skip-worktree'd file has a comment explaining how to un-flag it for real changes
  • Ghostty dark mode uses custom catppuccin-mocha-black.conf theme (pure black background)
  • install.sh handles compiling the Swift watcher, installing the launchd agent, and setting up symlinks

djensenius and others added 30 commits February 26, 2026 15:24
Switch between Catppuccin Mocha (dark) and Latte (light) based on:
- macOS: Swift daemon watches system appearance changes (instant)
- Non-macOS: Sunrise/sunset from ~/.config/tz.conf or 7AM-7PM fallback

Ghostty and Neovim handle switching natively. All other tools are
switched via scripts/switch-theme.fish which updates configs and
reloads tmux. Fish universal variables propagate BAT_THEME, FZF
colors, and DARK_MODE to all running shells instantly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Symlink files (bottom.toml, eza/theme.yml, yazi/theme.toml,
gh-dash/config.yml) are now gitignored — the dark/light variant
files remain tracked. install.sh creates the initial symlinks.

Sed-modified files (starship.toml, gitconfig, atuin/config.toml,
zellij/config.kdl, btop/btop.conf, k9s/config.yaml) use git
skip-worktree to hide runtime theme changes from git status.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- _sed_i now resolves symlinks before in-place edit (macOS sed
  can't operate on symlinks directly)
- Swift watcher uses /opt/homebrew/bin/fish instead of env lookup
  (launchd has a limited PATH)
- install.sh symlinks switch-theme.fish to ~/.local/bin/

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Tmux: sed the @catppuccin_flavor in tmux.conf before sourcing
  (previously set-then-source caused source to overwrite the value)
- Neovim: send 'set background=light/dark' to all running instances
  via --remote-send to nvim sockets
- Added tmux/tmux.conf to skip-worktree list

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Theme watcher: replaced notification-based approach with 2-second
  polling of AppleInterfaceStyle (notifications don't work reliably
  for CLI binaries launched by launchd)
- Tmux: sed the flavor in tmux.conf before sourcing so it takes effect
- Neovim: send 'set background=light/dark' to all running instances
  via nvim --remote-send to socket files
- install.sh: symlink switch-theme.fish to ~/.local/bin/ and add
  tmux/tmux.conf to skip-worktree

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The catppuccin/tmux plugin uses -ogq (only set if not set) for all
theme variables. On flavor switch, the old @thm_* values persisted
and the new theme file was silently ignored. Fix by unsetting all
@thm_, @catppuccin_, and @_ctp_ variables before re-sourcing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Source tmux.conf before re-sourcing catppuccin plugin so user
settings (window_status_style rounded, window_flags icon) are set
before the plugin's -ogq defaults. Then source tmux.conf again
after plugin for post-catppuccin overrides.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bump low-contrast colors from Surface2 (#ACB0BE) and Overlay1
(#8C8FA1) to Overlay2 (#7C7F93) and Subtext1 (#5C5F77) for
better readability on a light background. Affects permissions,
punctuation, metadata, and secondary text elements.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add Catppuccin Latte fish_color_* values to the theme handler.
The -lah flags (fish_color_option) switch from Mocha green (#a6e3a1)
to Latte green (#40a02b), and params from Mocha flamingo (#f2cdcd)
to Latte flamingo (#dd7878) — both much more readable on light bg.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…theme

fish_frozen_theme.fish (sorted alphabetically after 01-theme.fish)
was overwriting the Latte colors with hardcoded Mocha values. By
renaming to 99-theme.fish, the dynamic theme handler loads last
and correctly overrides the frozen defaults.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fish_frozen_theme.fish loaded after 99-theme.fish and overwrote
the Latte colors with hardcoded Mocha values. All fish_color_*
management is now handled dynamically by 99-theme.fish.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
go.nvim already runs golangci-lint via its linter config. Having
nvim-lint also trigger it caused 'parallel golangci-lint is running'
errors since golangci-lint doesn't support concurrent execution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Set vim.o.background from ~/.config/theme-mode so catppuccin picks
the correct flavour (latte/mocha) on launch. Changed flavour from
hardcoded 'mocha' to 'auto' to respect the background setting.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add background = 000000 to ghostty/config when switching to dark,
remove it when switching to light. Ghostty config added to
skip-worktree list.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Created catppuccin-mocha-black.conf theme with background = 000000
instead of Mocha's default #1e1e2e. Ghostty's native light/dark
theme switching now uses this directly, so the black background
applies to the entire terminal — not just tmux.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Many Go CLI tools (lipgloss, glamour) use COLORFGBG to detect
light/dark terminal background. Setting this ensures correct
theme detection for tools like gh copilot in new sessions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fish_variables stores THEME_MODE universal variable which changes
on every light/dark switch. Skip-worktree prevents it from showing
as modified in git status.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each file modified by the theme switcher at runtime now has a
comment explaining it is skip-worktree'd and how to un-flag it
when making real config changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Shorten skip-worktree comment in k9s/config.yaml to fit 80 char limit
- Quote $(id -u) in install.sh launchctl commands (SC2046)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…itch script

Replace the fragile git skip-worktree approach with a .gitattributes
clean filter that normalizes catppuccin theme strings (latte→mocha)
on git diff/add. This eliminates issues with git stash, checkout,
and branch switching.

Also condense switch-theme.fish from 169 to 119 lines by replacing
repetitive if/else blocks with data-driven loops for sed swaps and
symlink swaps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The theme switcher was doing a global replace of catppuccin_mocha↔latte
across the entire starship.toml, which renamed palette definition headers
like [palettes.catppuccin_mocha] causing TOML duplicate key errors.
Now only the 'palette = ' line is modified.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace external script-driven theme switching with native detection
for 4 tools, significantly reducing switch-theme.fish complexity.

tmux: Add client-dark-theme/client-light-theme hooks (tmux 3.6+)
  - New catppuccin-reset.conf unsets all theme vars for clean re-apply
  - Ghostty reports appearance changes → tmux fires hooks automatically
  - Eliminates sed swap, variable clearing loop, triple source-file dance

bat: Switch to BAT_THEME=auto:system with BAT_THEME_DARK/LIGHT env vars
  - bat auto-detects terminal color scheme per invocation
  - No daemon or script needed

yazi: Use native [flavor] dark/light config
  - New theme.toml with flavor section replaces symlink-swapped theme files
  - Catppuccin flavors installed locally with tmtheme.xml for syntax highlighting
  - Removes yazi from symlink swap loop

fish: Refactor to use native fish_terminal_color_theme (fish 4.x)
  - Replaces custom THEME_MODE universal variable with fish's built-in
    OSC 11 terminal background detection
  - Removes initialization block and THEME_MODE propagation

delta: Remains using sed swap (blocked by upstream issue #1678 — no
  dual-theme config support yet)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
No longer needed — theme detection now uses fish 4.x native
fish_terminal_color_theme instead of a custom universal variable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Yazi now uses native flavor system (theme.toml is a real file, not a symlink).
fish_variables no longer contains THEME_MODE, so the normalize filter is unused.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The .gitattributes clean filter handles theme normalization now,
so skip-worktree is no longer needed for these files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add ,tG for git status view
- Add ,tS for document symbols view
- Move buffers from ,tb to ,tB
- Move toggle from ,tg to ,tT

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant