.dotfiles

Guides

Theming Guide

The dotfiles ship a wallpaper-driven theme system that generates terminal color palettes directly from wallpaper images using K-Means clustering in CIELAB color space. One command changes the terminal, editor, window manager, GTK, desktop environment, wallpaper, and browser-facing color mode in under a second.

Themes are not hand-crafted — they are extracted from whatever wallpapers are available on the system.

How Themes Work

Wallpapers are the source of truth. The system discovers wallpapers from two locations:

  1. System wallpapers — platform-native (macOS /System/Library/Desktop Pictures/, Linux /usr/share/backgrounds/)
  2. Custom wallpapers — user-provided in ~/Pictures/Wallpapers/ (custom overrides system)

extract-theme.py extracts dominant colors from each wallpaper using K-Means clustering in CIELAB color space, then generates a full terminal palette (16 ANSI colors, accent, bg/fg, panel, border) with WCAG contrast enforcement. Dark variants map wallpaper hues to luminous color blocks with black text; light variants map them to deep blocks with white text, so both modes remain vivid and readable.

The mode-specific tuning follows Apple's semantic-color model: light and dark are generated independently, backgrounds use a primary/secondary/tertiary surface hierarchy, and chromatic roles are calibrated against Apple's increased-contrast system colors while retaining the wallpaper hue. Terminal windows are opaque so translucency cannot invalidate the generated sRGB contrast. This applies consistently to Ghostty, Kitty, Alacritty, WezTerm, Foot, iTerm2, Warp, tmux, and provider TUIs.

rebuild-themes.sh orchestrates discovery → extraction → assembly into .chezmoidata/themes.toml. Themes are cached in ~/.cache/dotfiles/themes/ and only regenerated when wallpapers change.

The theme key in .chezmoidata.toml controls the active theme. Every template references the active theme's data through {{ $t := index .themes .theme }}.

Switching Themes

Interactive Picker

dot theme

Opens an fzf picker listing every paired wallpaper theme (themes that have both -dark and -light variants). Two columns: WALLPAPER name and SOURCE (System or Custom). The current theme is marked with ✓ and ◀. Select one and press Enter.

Direct Switch

dot theme tahoe-dark

Sets the theme immediately. Regenerates configs and reloads running applications.

Preview the Transaction

dot theme plan maui --mode auto
dot theme plan maui --mode auto --json

Planning is pure: it does not acquire the apply lock, create state, render a template, reload an application, or use the network. JSON plans conform to schemas/theme-plan.schema.json and include the operation identity, previous and desired theme identities, installed targets, required/optional status, and pre-change hashes.

Rebuild Themes

dot theme rebuild           # incremental (uses cache for unchanged wallpapers)
dot theme rebuild --force   # full regeneration
dot theme rebuild --list    # discover wallpapers without rebuilding

Discovers wallpapers from system + custom paths, runs K-Means extraction in parallel (4 jobs), caches generated themes in ~/.cache/dotfiles/themes/, and writes .chezmoidata/themes.toml. Custom wallpapers override system wallpapers on name collision.

Under the Hood: dot-theme-sync

dot-theme-sync handles the switching pipeline as a per-user transaction:

  1. Acquires an exclusive, portable user lock and records its PID and operation ID.
  2. Snapshots installed file targets, including symlink identity, into a private operation directory.
  3. Writes machine-local runtime state to ~/.config/chezmoi/chezmoi.toml; the tracked .chezmoidata.toml remains the fresh-install default.
  4. Runs a targeted chezmoi apply and treats renderer failures as required transaction failures.
  5. Updates optional AI-provider fragments and reloads installed applications.
  6. Reads back the active theme identity, writes a versioned JSON journal, and releases the lock.
  7. On a required failure or signal, restores files in reverse order and records rolled_back or rollback_failed.
dot-theme-sync                    # Reload current theme
dot-theme-sync macos-wave-light   # Switch to a new theme
dot-theme-sync maui-dark --plan --json
dot-theme-sync --full             # Full chezmoi apply instead of targeted

Journals and snapshots are retained under $XDG_STATE_HOME/dot/theme-transactions/ (default ~/.local/state/dot/theme-transactions/) for the latest 20 operations. File mutations are reversible in this milestone; desktop IPC and system appearance reloads remain best-effort adapters and are explicitly reported as optional results.

AI CLI provider files remain user-owned. The adapter validates JSON or TOML, checks that the file hash has not changed immediately before its atomic rename, and reports invalid_config, conflict, or write_failed without replacing the file. Disable all provider updates, or one provider, in the machine-local chezmoi configuration:

[data.features]
ai_theme_sync = false

[data.features.ai_theme_providers]
gemini = false

What Changes

Each theme switch touches these applications:

Application Mechanism What Changes
Ghostty chezmoi apply + macOS app-support sync + DBus reload-config or runtime signal fallback Background, foreground, all 16 ANSI colors, cursor
Tmux chezmoi apply + source-file Wallpaper colors, deterministic session identity, focus/prefix state, pane borders, mode indicators
AI CLIs terminal ANSI + COLORFGBG + provider adapters Codex custom syntax theme; Claude auto; Gemini/Qwen ANSI light/dark; agy terminal; OpenCode system; Aider mode and semantic colors
Niri chezmoi apply + load-config-file IPC Window borders, focus ring, inactive tint
Desktop (macOS) osascript + defaults write + killall System appearance (Light/Dark), accent color, highlight color; forces SystemUIServer/Dock/cfprefsd refresh
Wallpaper (macOS) osascript System Events Desktop wallpaper set across all displays
Wallpaper (Linux) gsettings / dms / swaybg / feh HEIC auto-converted to PNG; picture-uri and picture-uri-dark set separately
Desktop (Linux/GNOME) chezmoi apply + gsettings Theme name, icon theme, color scheme preference
Safari / Chrome / Edge Native browser appearance follows desktop theme Browser chrome stays aligned when using the default/native browser theme
Firefox chezmoi apply on ~/.config/firefox/user.js Website color scheme preference follows the active dot theme; link that file into a Firefox profile to enforce it
DMS sed -i on settings.json + IPC Stock theme mapped to accent family, dark/light mode
Neovim --remote-expr Lua eval over socket Colorscheme, style variant, background mode
VS Code chezmoi apply on settings.json workbench.colorTheme value
Alacritty chezmoi apply Full color block regeneration
Kitty chezmoi apply Full color block regeneration
WezTerm chezmoi apply Color scheme in Lua config

Dark/Light Toggle

dot theme toggle

Toggles between the dark and light variant of the current theme family. A theme named macos-tahoe-dark toggles to macos-tahoe-light, and vice versa.

Theme Families

Available themes depend on your system. Run dot theme list to see what's discovered. On macOS Sonoma, you'll see ~150+ themes from system wallpapers. Custom wallpapers in ~/Pictures/Wallpapers/ add more.

Rebuilding themes

When wallpapers change (new system update, new custom wallpapers), regenerate:

dot theme rebuild          # Regenerate (uses cache for unchanged wallpapers)
dot theme rebuild --force  # Force full regeneration
dot theme rebuild --list   # List discovered wallpapers without rebuilding

What works without wallpapers

Theme switching is a two-tier system:

Core (always works) — ships in the repo, no setup needed:

  • Terminal colors (Ghostty, Alacritty, Kitty, WezTerm, tmux)
  • Editor themes (Neovim colorscheme, VS Code)
  • macOS dark/light mode and accent color
  • Linux GNOME color-scheme, GTK theme, icon theme
  • Browser color mode (Safari, Chrome, Firefox)

Wallpapers (optional) — user-provided, enhances the theme:

  • Desktop wallpaper matched to the active theme
  • Requires ~/Pictures/Wallpapers/ with files named macos-NAME-dark.heic

If no wallpapers are present, dot theme applies all core changes and skips the wallpaper step. No errors, no manual config.

Wallpapers (optional)

Wallpapers are not shipped in the repo. Each user sources their own and places them in ~/Pictures/Wallpapers/:

macos-tahoe-dark.heic
macos-tahoe-light.heic

The naming convention is macos-NAME-APPEARANCE.heic (or .jpg/.png). The theme picker marks themes with matching wallpapers as [W].

Wallpaper guidelines

  • Resolution: 6016x6016 recommended (matches Apple's native resolution)
  • Format: .heic preferred on macOS, .png/.jpg also supported
  • Brightness: dark/light pairs targeting a golden ratio (1.618) relationship give balanced contrast across displays

Platform behavior

Platform Wallpaper support Mechanism
macOS .heic, .jpg, .png osascript (all desktops)
Linux (GNOME) .png, .jpg (.heic auto-converted) gsettings picture-uri + picture-uri-dark
Linux (Wayland) .png, .jpg (.heic auto-converted) swaybg, feh, or Niri/DMS IPC
WSL Not applicable No compositor; terminal colors still apply

On Linux, .heic files are automatically converted to .png using magick, heif-convert, or convert (whichever is available). The .png is cached and only regenerated when the source .heic changes.

Using your OS default wallpapers

If you don't provide custom wallpapers, your OS keeps its current desktop wallpaper. The theme still applies all color changes (terminal, editor, accent, dark/light mode). This is the expected default for most users.

Build Artifacts

Build caches (Cargo, Go, pip, uv, Zig) are redirected below the private per-user $DOT_BUILD_ROOT, which defaults to $XDG_CACHE_HOME/dot/builds. mise exports the root for managed commands, while Fish and Zsh create it with owner-only permissions during shell initialization. The cache is disposable and can be removed when no build is running.

Troubleshooting

Theme switch did not apply

Run a full apply to force all configs:

dot-theme-sync --full

Ghostty did not reload

Ghostty reloads via DBus (com.mitchellh.ghostty / reload-config). If DBus is unavailable, the fallback sends SIGUSR2 to the main process and also matches the macOS app bundle path when needed. Verify Ghostty is running:

pgrep -x ghostty

On macOS, Ghostty may also read ~/Library/Application Support/com.mitchellh.ghostty/config. dot-theme-sync now mirrors the regenerated XDG config into that location before reloading so the app-support override cannot keep an older palette active.

Neovim did not change colors

dot-theme-sync finds Neovim server sockets at /tmp/nvim*/0 and $XDG_RUNTIME_DIR/nvim.*.0. If Neovim runs with a custom --listen path, the auto-discovery misses it. Restart Neovim to pick up the new theme from the regenerated config.

GTK theme looks wrong

GTK theme names must match installed themes exactly. Catppuccin themes use names like catppuccin-mocha-blue-standard+default. Install the matching GTK theme package or fall back to Adwaita-dark / Adwaita.

macOS accent or appearance did not update

dot-theme-sync applies macOS appearance using osascript and accent via:

defaults write -g AppleAccentColor -int <value>

dot-theme-sync now kills cfprefsd, SystemUIServer, Dock, and System Settings after writing accent/highlight defaults to force an immediate refresh. If the UI still does not update, close and reopen System Settings.

Browser theme did not change

Safari, Chrome, and Edge are coordinated through the desktop theme, so custom browser themes can override what dot-theme-sync is trying to align. Switch those browsers back to their native/default theme if you want them to track macOS or GTK automatically.

Firefox uses the managed file at ~/.config/firefox/user.js. Link that file into your active Firefox profile as user.js if you want dot-theme-sync to control website prefers-color-scheme behavior:

ln -sf ~/.config/firefox/user.js ~/.mozilla/firefox/<profile>/user.js

tmux shows old colors

Tmux reloads via source-file. If TPM plugins override colors, run:

tmux source-file ~/.config/tmux/tmux.conf

Each session hashes its name into twelve shades derived from the active theme's primary, secondary, and tertiary colors. Collision probing keeps the active session set visually distinct; renaming a session updates that identity automatically. The session name is the bar's only persistent colored element. Windows, directory, system health, date, and time use neutral semantic text; the directory remains visible in split terminal windows while width gates remove monitoring and date from narrower AI panes. CPU, memory, and battery are gathered by one lightweight status helper on macOS, Linux/Arch, WSL, and Windows environments. Set @dot_status_show_path, @dot_status_show_system, or @dot_status_show_date to off before reloading tmux to hide an individual module.

AI provider TUIs receive the same mode and palette through the most native interface each exposes. COLORFGBG is injected into new shells and tmux panes for provider-neutral detection. dot-theme-sync also preserves and updates installed provider configs for Codex, Claude, Gemini, Antigravity (agy), Qwen, and OpenCode; Aider reads generated mode and color environment variables. Restart an already-running provider TUI after a theme change because most cache their appearance at startup.

Checking the active theme

grep '^theme = ' ~/.dotfiles/.chezmoidata.toml

This prints the current theme name. Cross-reference with dot theme list for available options.