Unified theme management for terminal and desktop applications. Apply consistent color schemes across Ghostty, Kitty, tmux, btop, Neovim, and more from a single command — with themed backgrounds, opacity control, and cross-machine sync.
- 20+ themes including Gruvbox, Rose Pine, Kanagawa, Nordic, Flexoki, and more
- Cross-platform: macOS, Linux (Arch/Hyprland), WSL
- Themed backgrounds: generated or recolored wallpapers that follow the active theme
- Opacity control: adjust terminal transparency across apps from one command
- Cross-machine sync: GitHub Gist synchronization keeps preferences in sync
- Smart tracking: like/dislike themes, track usage time, filter out rejected themes
- Neovim integration: themes match Neovim colorschemes (plugin or generated)
curl -fsSL https://raw.githubusercontent.com/datapointchris/theme/main/install.sh | bashOr manually:
git clone https://github.com/datapointchris/theme.git ~/.local/share/theme
ln -sf ~/.local/share/theme/bin/theme ~/.local/bin/themeCore:
jq— JSON processingyq— YAML processing (mikefarah/yq, the Go implementation)fzf— interactive theme pickergh— GitHub CLI (for the sync feature)
Optional:
bat— syntax-highlighted output intheme logimagemagick(convert) — theplasmaandlowpolybackgroundsgowall— therecolorbackgroundascii-image-converter— theasciibackground
- Install, as above, and make sure
~/.local/binis on yourPATH. theme verifychecks that the theme library andyqare in place, and shows the current theme once one is applied.theme changeopens a picker with a live color preview.theme listthentheme apply <theme-id>does the same without one. The apply writes each supported app's config and reloads the apps that can reload in place.- Optional:
theme sync initcreates a secret GitHub Gist throughgh, so your ratings and history follow you to other machines. - Optional:
theme schedule onchanges the theme on a timer while you are at the machine, sotheme ranklearns which themes you keep.
Running theme with no arguments prints the full help.
theme list # List available themes
theme list --status rejected # List rejected themes, with the reason for each
theme list --status all # List both
theme change # Interactive picker with color preview
theme apply <theme-id> # Apply a specific theme
theme current # Show current theme and stats
theme random # Apply a random theme, weighted by time since last use
theme info [theme] # Browse themes and view detailed history
theme verify # Check the theme system is healthy| Code | Meaning |
|---|---|
| 0 | Success. For verify and schedule status, nothing needs attention |
| 1 | The command ran and failed |
| 2 | Usage error: an unknown command, a bad option, or a missing argument |
| 3 | verify found an error, or schedule status found a timer that is on but records no check |
theme like "great contrast" # Mark current theme as liked
theme dislike "too dim" # Mark as disliked
theme note "good for coding" # Add a freeform note
theme reject "hurts my eyes" # Remove from rotation (hidden from list)
theme unreject # Restore a rejected theme (interactive picker)
theme unreject <theme-id> # Restore one by its id, with no pickerchange, and info and unreject without a theme, open a picker. Without a
terminal on stdin, or with --no-input anywhere on the line, they exit 2 and
name the command that takes the theme as an argument instead.
theme rank # Rankings: by likes, by hours used, and by scheduled turns kept
theme log # View complete historyChanges the theme on a timer, so the history shows which themes you switch away from and which you leave on:
theme schedule on # Install the timer: systemd on Linux and WSL, launchd on macOS
theme schedule status # Whether it is on, and when the next change is due
theme schedule off # Remove the timerThe theme changes at fixed local hours, at the first check in the hour after each
that finds you at the machine. A turn you are away for is skipped, so changes missed
overnight never all land on your return. A theme you applied by hand holds through
the turns for longer. theme schedule --help gives the hours and the hold.
theme rank counts each theme's scheduled turns as kept or switched
away from. On Hyprland, telling idle from active needs a hypridle listener, which
theme schedule --help shows. On WSL, the timer needs systemd enabled in
/etc/wsl.conf, and presence is a recent keystroke in a WSL terminal.
Each theme can drive a matching desktop background. Four modes: plasma draws a
fractal cloud from the palette alone, while ascii, lowpoly and recolor
transform one of your own photos — into characters, into triangles, or into the
theme's colors.
theme background # Show background usage/help
theme background current # Show the active background
theme background rotate # Pick a new background, keep the theme
theme background list # List available backgrounds and current mode
theme background like # Rate the current background (per theme)
theme background rank # Rankings for this theme (--global for all)
theme background pre-generate # Pre-render all backgrounds for instant switching
# Which kinds of background to draw from
theme background mode current
theme background mode set generated:plasma recolor
theme background mode all
# Source images used by 'recolor' mode
theme background source add ~/Pictures/wallpapers
theme background source list
theme background source verifytheme opacity # Show opacity usage/help
theme opacity current # Show current opacity
theme opacity set 90 # Set opacity to 90%
theme opacity up # Increase by 5%
theme opacity down # Decrease by 5%Restart the terminal to see opacity changes.
theme browsers # Show detected browsers and theme statusFirefox-based browsers and Vivaldi are themed automatically on theme apply.
Firefox-based browsers require
toolkit.legacyUserProfileCustomizations.stylesheets = true in about:config,
then a restart, for userChrome.css to take effect.
Vivaldi gets a native Vivaldi theme, selected for both its light and dark schedule. Vivaldi rewrites its own settings when it quits, so an apply while it is open waits for it to exit and installs the theme then. The theme shows at its next launch.
Sync your theme preferences across machines using a GitHub Gist:
theme sync init # One-time setup (creates gist)
theme sync status # Show sync status
theme sync push # Manual push
theme sync pull # Manual pull
theme sync off # Disable sync
theme sync on # Re-enable syncAfter initialization, sync happens automatically in the background.
theme --version # Show current version
theme update # Update to the latest tagged releaseReleases are automated via go-semantic-release. The update command only checks out tagged releases, so you always land on a stable version.
theme also checks once a day and prints one line when a newer release exists:
theme v4.12.0 available (running v4.11.0) — run `theme update`
It never installs anything and never prints an error — a failed check is recorded and swallowed, because an update notice must not break the command you typed. Nothing is printed when either stream is not a terminal, under CI, from a checkout that is not sitting on a release tag, or within the interval. Set NO_AUTO_UPDATE or THEME_NO_AUTO_UPDATE to turn it off, and THEME_AUTO_UPDATE_INTERVAL=6h to change the cadence.
Both commands come from bashselfupdate, which install.sh installs. theme runs fine without it; only these two features need it.
- Ghostty — terminal emulator (colors + tab CSS)
- Kitty — terminal emulator
- tmux — terminal multiplexer
- btop — system monitor (reloads in place from btop 1.3.1)
- abtop — AI agent monitor. Needs the fork
that reads theme files and reloads in place, with
theme = "current"in~/.config/abtop/config.toml - TUIs on gotermstyle — the
theme's selection, accent, markdown and syntax colors, in
~/.config/tui-theme/current.json. A running program rereads it on SIGUSR2, whichtheme applysends to each reader it names inTUI_THEME_READERS - bat — syntax highlighter / pager
- delta — git diff pager (requires bat; include
~/.config/delta/current.gitconfigfrom your gitconfig) - yazi — file manager
- sioyek — PDF viewer (custom color mode)
- Neovim — text editor (via colorscheme plugins or generated)
- Browsers — Firefox-based (Zen, Librewolf, Firefox, Thunderbird) and Vivaldi
- Wallpaper — themed desktop background, set through Finder on macOS, hyprpaper on Hyprland, and the Windows host on WSL
- JankyBorders — window border highlights
- Hyprland — window manager colors (needs the Lua config of Hyprland 0.56 or
later; add
pcall(require, 'themes.current')tohyprland.luaafter your own colors) - Hyprlock — lock screen
- Waybar — status bar
- Rofi — application launcher
- Dunst / Mako — notification daemons
- Windows Terminal — terminal colors
Additional generators (Alacritty, VS Code, walker, swayosd, GTK icons) live in
lib/generators/. Run them through lib/generate-all.sh, never one at a time —
the script owns the generator-to-filename map, and a one-off invocation leaves a
theme with a file set no other theme has. VS Code differs only in that theme apply does not deploy its output; it is generated with everything else.
Plugin themes pair an existing Neovim colorscheme with generated app configs. The plugin's author tuned the editor colors by hand and this tool matches everything else to them, which in practice beats generating a colorscheme from the same palette.
Generated themes build both the app configs and a Neovim colorscheme from a
single theme.yml, for palettes with no Neovim plugin behind them.
A plugin theme's theme.yml is a snapshot of that plugin's palette taken by
hand. Nothing re-reads the plugin, so if you update a colorscheme plugin and its
author has changed a color, Neovim moves and the other apps do not — re-check the
palette after updating one.
theme list shows every theme. To see which kind each one is, and which upstream
plugin a theme matches, read it from the source of truth:
yq -r '[.meta.display_name, .meta.neovim_colorscheme_source, (.meta.plugin // "-")] | @tsv' \
themes/*/theme.yml | column -t -s$'\t'Data and configuration live in the XDG base directories. The paths below are
the defaults, used when XDG_STATE_HOME, XDG_CONFIG_HOME and XDG_CACHE_HOME
are unset:
~/.local/state/theme/
├── history.jsonl # Usage/rating history (synced)
├── current # Current theme ID
├── schedule.json # Outcome of the last scheduled check
├── sync-state.json # Sync configuration
└── background.png # Current wallpaper, for a hyprpaper.conf to name
~/.config/theme/
├── background-mode # Enabled background modes
└── background-sources.conf # Source image references for recolor mode
~/.cache/theme/backgrounds/ # Pre-generated background cache
Opacity is stored per-app under each app's config directory (e.g.
~/.config/ghostty/opacity, ~/.config/tmux/opacity.conf).
- Create a directory in
themes/with your theme ID - Create
theme.ymlwith color definitions:
meta:
id: "my-theme"
display_name: "My Theme"
neovim_colorscheme_source: "plugin" # or "generated"
plugin: "author/nvim-theme" # if using a plugin
variant: "dark"
base16:
base00: "#1d2021" # Background
base01: "#3c3836" # Lighter background
# ... base02 through base0F
ansi:
black: "#282828"
red: "#cc241d"
# ... all 16 ANSI colors
special:
background: "#1d2021"
foreground: "#ebdbb2"
cursor: "#ebdbb2"-
Generate every app config in one call:
lib/generate-all.sh --themes my-theme
Then
theme apply my-theme. A plugin theme also needs the Neovim plugin named in itsmeta.plugininstalled, for example as a lazy.nvim spec. A Neovim config can build those specs by readingmeta.pluginfrom eachthemes/*/theme.yml, so a new theme needs no editor-side edit.CLAUDE.mdcovers how Neovim follows the applied theme and how to map an upstream palette into the base16 slots.
MIT