A tiny, colorscheme-agnostic theme manager for Neovim.
The plugin knows nothing about any specific colorscheme. Each theme is just a
name plus a config function you write (it typically calls some plugin's
setup() and sets vim.o.background). The plugin:
- runs your
config, then applies the colorscheme, - persists the choice across sessions,
- derives a normalised UI palette from standard highlight groups and exposes
it on
vim.g.theme_palette(so a statusline/tabline can stay in sync with any colorscheme — no per-theme colour tables), - provides a centred FzfLua picker with live full-editor preview.
Pairs with a theme-switch shell script (tmux + terminal); the two share only
theme names by convention.
- Neovim >= 0.9
ibhagwan/fzf-lua(picker)- Whatever colorscheme plugins you reference in your
configcallbacks
{
"spenserlee/theme-switch.nvim",
dependencies = { "ibhagwan/fzf-lua" },
lazy = false,
priority = 999, -- after colorscheme plugins are on the rtp
-- The plugin registers no keymap; map the picker yourself. A `keys` spec
-- works even before the plugin's setup() has run.
keys = {
{ "<leader>cs", "<cmd>ThemeSwitch<cr>", desc = "Theme: switch" },
},
opts = {
default = "everforest-dark-harder",
themes = {
["everforest-dark-harder"] = {
colorscheme = "everforest",
config = function()
vim.o.background = "dark"
require("everforest").setup({ background = "harder", italics = true })
end,
},
["gruvbox-light"] = {
colorscheme = "gruvbox",
config = function()
vim.o.background = "light"
require("gruvbox").setup({
contrast = "medium",
-- gruvbox leaves Normal transparent; pin it so the editor bg is light
overrides = { Normal = { bg = "#fbf1c7" }, SignColumn = { bg = "#fbf1c7" } },
})
end,
},
},
},
}Adding a new colorscheme is just: install its plugin, add one themes entry
with a config callback. The plugin never needs to change.
| field | type | description |
|---|---|---|
colorscheme |
string | name passed to :colorscheme |
config |
function? | runs before applying: set background, call setup() |
after |
function? | runs after applying: extra highlight tweaks |
term_bg |
string? | optional #rrggbb pushed to the terminal via OSC 11 |
If term_bg is omitted, the resolved Normal background is used.
local ts = require("theme-switch")
ts.apply("gruvbox-dark") -- apply (no persist)
ts.apply("gruvbox-dark", true) -- apply + persist
ts.load_saved() -- restore persisted theme (or default)
ts.list() -- { name, ... } (alphanumeric)
ts.pick() -- open the FzfLua picker (also :ThemeSwitch)apply is callable from outside Neovim, which is how a companion shell script
keeps running instances in sync:
nvim --server "$SOCK" --remote-send "<cmd>lua require('theme-switch').apply('gruvbox-dark', true)<cr>"After each apply, derived from standard highlight groups:
{
accent, accent_bg, -- from Function/String fg, TabLineSel bg
bar_bg, bar_dim, -- from TabLineFill / Normal bg
tab_active_fg, tab_active_bg,
tab_inactive_fg, tab_inactive_bg,
normal_bg, normal_fg,
}A tabline (e.g. tabby) can read these to stay in sync with the active theme, without the theme manager knowing anything about the colorscheme.
| option | default | description |
|---|---|---|
themes |
{} |
the theme table (required) |
default |
first (sorted) | fallback theme on first run |
state_file |
stdpath data | where the persisted choice is stored |
register_command |
true |
create the :ThemeSwitch command |
Themes are always listed alphanumerically in the picker.