Dynamically adapt your skin to the environment you're in, like a chameleon.
color-chameleon.nvim lets you set conditional rules for when a colorscheme should be applied.
These rules are evaluated in order from top-to-bottom; the first matching rule wins.
Rules are triggered on VimEnter, DirChanged, BufReadPost, BufNewFile, BufEnter, and TermOpen events.
color-chameleon-demo.mp4
β¨ What's New / π¨ Breaking Changes
- Context-Aware Colorschemes: Automatically switch colorschemes based on:
- Working directory (local vs remote/mounted filesystems)
- Environment variables (colorterm, sudo, TMUX, custom vars)
- Buffer properties (filetype, buffer type)
- Custom functions/conditions (any logic you can write)
- Flexible Logic: Combine conditions with AND logic, use arrays for OR logic
- Smart Switching: Preserves your previous colorscheme when leaving special contexts
- Buffer-Aware: Considers both global working directory and individual buffer paths
- Simple Configuration: Express your workflow through intuitive conditional rules
- Lightweight: Minimal performance impact
Install the theme with your preferred package manager, such as folke/lazy.nvim:
{
"uhs-robert/color-chameleon.nvim",
lazy = false,
priority = 900,
config = function()
require("color-chameleon").setup({
rules = {
{ path = "~/mnt/", colorscheme = "gruvbox" },
},
default = "oasis"
})
end
}Or via packer.nvim:
use {
"uhs-robert/color-chameleon.nvim",
config = function()
require("color-chameleon").setup({
rules = {
{ path = "~/mnt/", colorscheme = "gruvbox" },
},
default = "oasis"
})
end
}Each rule can have the following fields:
colorscheme(required: string) - The colorscheme to apply when this rule matches.background(optional: string) - Background setting to apply ("light"or"dark"). Applied before the colorscheme.path(optional: string or array) - Directory path(s) to match (e.g.,"~/mnt/","~/work/client-a/",{"~/work/client-a, "~/work/client-b/"}. Use an array to match any of multiple paths.buftype(optional: string or array) - Buffer type(s) to match (e.g.,"terminal","help", or{"quickfix", "nofile"}). Use an array to match any of multiple buffer types.filetype(optional: string or array) - Buffer filetype(s) to match (e.g.,"markdown","python", or{"lua", "vim"}). Use an array to match any of multiple filetypes.env(optional: table) - Environment variable conditions to check.- Key: environment variable name
- Value:
true= check if variable existsfalse= check if variable doesn't exist- string = check if variable equals this exact value
condition(optional: function) - Custom function that receives the current working directory and returns a boolean.
Important
Rules are evaluated in order. The first matching rule wins.
All conditions in a rule must match (AND logic). For example, a rule with both path and filetype will only match if the current directory matches the path AND the buffer filetype matches.
Arrays within a field use OR logic. For example, filetype = {"json", "yaml"} matches if the filetype is "json" OR "yaml".
Refer to Usage below for examples of each and Use Cases for real world examples.
The power is in your hands; create rules that match your unique workflow.
Below are practical examples organized from simple to advanced. Start with the basics, then explore the combinations that fit your needs.
Switch theme when in a specific directory:
require("color-chameleon").setup({
rules = {
{ path = "~/mnt/", colorscheme = "oasis-mirage" }, -- check out oasis.nvim for a cool colorscheme pack!
},
default = "oasis", -- Optional: colorscheme when no rules match
})ποΈ Multiple Directories
Different themes for different directories:
require("color-chameleon").setup({
rules = {
{ path = "~/work/", colorscheme = "tokyonight" },
{ path = "~/personal/", colorscheme = "catppuccin" },
{ path = "~/mnt/", colorscheme = "oasis-mirage" },
},
})π¦οΈ Environment-Based Switching
Change themes based on any vim.env variable. These are variables set by Neovim's startup environment (vim.env).
require("color-chameleon").setup({
rules = {
{ colorscheme = "gruvbox", env = { COLORTERM = "truecolor" } }, -- Applies when truecolor is supported
{ colorscheme = "tokyonight", env = { TMUX = true } }, -- Applies when in TMUX
{ path = "~/work/", colorscheme = "catppuccin", env = { WORK_ENV = "production" } }, -- Applies when path AND env are both true
},
})Boolean values like true|false simply check for existence while string values check exact matches like so:
rules = {
-- Check if variable exists
{ colorscheme = "gruvbox", env = { TMUX = true } },
-- Check if variable doesn't exist
{ colorscheme = "tokyonight", env = { TMUX = false } },
-- Check for exact value
{ colorscheme = "nord", env = { NODE_ENV = "production" } },
-- Multiple conditions (all must match)
{ colorscheme = "catppuccin", env = { TMUX = true, COLORTERM = "truecolor" } },
}Use :ChameleonEnv to see the ENV variables being used in your current environment.
[!IMPORTANT]
These variables are inherited when Neovim launches and do not change during runtime.
π Buffer Properties (Filetype & Buftype)
Change theme based on buffer properties like filetype or buffer type:
require("color-chameleon").setup({
rules = {
-- Simple filetype matching
{ filetype = "markdown", colorscheme = "nord" },
{ filetype = "python", colorscheme = "gruvbox" },
-- Buffer type matching
-- NOTE: buffers which open a split like "help" are ignored
{ buftype = "terminal", colorscheme = "tokyonight" },
-- Combine path + filetype (both must match)
{
path = "~/notes/",
filetype = "markdown",
colorscheme = "nord"
},
-- Combine path + buftype
{
path = "~/work/",
buftype = "terminal",
colorscheme = "gruvbox"
},
},
})Common filetypes: markdown, python, lua, javascript, typescript, rust, go, etc.
Common buftypes:
""(empty string) - Normal file buffers"terminal"- Terminal buffers"help"- Help documentation"quickfix"- Quickfix/location lists"nofile"- Scratch buffers
Refer to buftype for all.
π OR Logic with Arrays
You can use arrays to match any of multiple values (OR logic). This works for path, filetype, and buftype:
require("color-chameleon").setup({
rules = {
-- Match ANY of these filetypes
{ filetype = {"json", "yaml", "toml", "xml"}, colorscheme = "nord" },
-- Match ANY of these paths
{ path = {"~/work/client-a/", "~/work/client-b/"}, colorscheme = "gruvbox" },
-- Match ANY of these buffer types
{ buftype = {"terminal", "nofile"}, colorscheme = "catppuccin" },
-- Combine: Match ANY filetype AND a specific path (both conditions must match)
{
path = "~/notes/",
filetype = {"markdown", "text", "org"},
colorscheme = "tokyonight"
},
-- Multiple arrays: Match ANY path AND ANY filetype
{
path = {"~/projects/rust/", "~/projects/go/"},
filetype = {"rust", "go"},
colorscheme = "gruvbox"
},
},
})How it works:
- Arrays within a field use OR logic: match any value
- Multiple fields use AND logic: all must match
π Background Setting (Light/Dark Mode)
Set the background explicitly when applying a colorscheme. Useful for themes that adapt to light/dark backgrounds:
require("color-chameleon").setup({
rules = {
-- Use gruvbox dark for remote systems
{ path = "~/mnt/", colorscheme = "gruvbox", background = "dark" },
-- Use light background for reading documentation
{ filetype = {"markdown", "text"}, colorscheme = "everforest", background = "light" },
-- Dark background for terminal buffers
{ buftype = "terminal", colorscheme = "tokyonight", background = "dark" },
},
default = "nord",
background = "dark", -- Global default background
})[!NOTE]
The
backgroundsetting is applied before the colorscheme, allowing themes to adapt accordingly.
π§© Custom Conditions
Use custom functions for any logic you can imagine. The `condition` function receives the current working directory and should return a boolean:
require("color-chameleon").setup({
rules = {
-- Check for a specific file in the directory
{
path = "~/projects/",
colorscheme = "nord",
condition = function(cwd)
return vim.fn.filereadable(cwd .. "/.use-nord-theme") == 1
end
},
-- Use a different theme during night hours
{
colorscheme = "oasis-night",
background = "dark",
condition = function()
local hour = tonumber(os.date("%H"))
return hour >= 20 or hour < 6
end
},
},
})π€ Combine It All
Combine multiple conditions for powerful context-aware theming. All conditions in a rule must match:
local uid = (vim.uv or vim.loop).getuid()
local is_sudoedit = vim.env.SUDOEDIT == "1" -- This requires your shell's config to export a flag like: SUDO_EDITOR="env SUDOEDIT=1 /usr/bin/nvim"
local is_root = is_sudoedit or uid == 0
require("color-chameleon").setup({
rules = {
-- Custom condition for root context
{ colorscheme = "oasis-sol", condition = function() return is_root end },
-- Single condition for mount directory (remote files SSHFS)
{ path = "~/mnt/", colorscheme = "oasis-mirage" },
-- Combine path + environment
{ path = "~/work/", env = { NODE_ENV = "production" }, colorscheme = "tokyonight" },
-- Combine path + filetype
{ path = "~/notes/", filetype = "markdown", colorscheme = "catppuccin" },
-- ALL conditions combined: path + env + filetype + buftype + custom function
-- This rule only matches when ALL of these are true:
{
path = "~/projects/sensitive/", -- In sensitive projects directory
env = { COLORTERM = "truecolor" }, -- AND full terminal color support
filetype = {"lua", "md"}, -- AND editing a Lua or Md file
buftype = "", -- AND it's a normal file (not terminal/help/etc)
condition = function(cwd) -- AND a .secure marker file exists
return vim.fn.filereadable(cwd .. "/.secure") == 1
end,
colorscheme = "gruvbox"
},
},
default = "oasis", -- Default theme for normal contexts
})Tip
Now that you know how the plugin works, check out Use Cases below for some real world examples.
Color Chameleon provides the following commands:
:ChameleonStatus- Show current status and configuration:ChameleonToggle- Toggle camouflage mode on/off:ChameleonEnable- Enable camouflage mode:ChameleonDisable- Disable camouflage mode:ChameleonEnv- Show your currentvim.envvariables:ChameleonInspect- Inspect the current buffer and evaluate rule matches:ChameleonReload- Reload configuration and reapply rules:ChameleonDebug- Toggle debug mode
Lua API
local chameleon = require("color-chameleon")
-- Enable/disable programmatically
chameleon.toggle()
chameleon.enable()
chameleon.disable()
-- Check status
chameleon.status()
-- Run to inspect buffer/window properties and to see what rules match in current context
chameleon.inspect()
-- List environment variables
chameleon.env()
-- Reload configuration
chameleon.reload()
-- Toggle debug mode
chameleon.debug()Default keybindings, when enabled, under <leader>C (fully customizable):
| Keymap | Command | Description |
|---|---|---|
<leader>Cc |
:ChameleonToggle |
Toggle camouflage mode |
<leader>Cv |
:ChameleonEnv |
Show environment variables |
<leader>Cs |
:ChameleonStatus |
Show chameleon status |
<leader>Cd |
:ChameleonDebug |
Toggle debug mode |
<leader>Cr |
:ChameleonReload |
Reload configuration |
<leader>Ci |
:ChameleonInspect |
Inspect context and rule matching |
Refer to configuration below on how to disable or customize.
require("color-chameleon").setup({
enabled = true, -- Set to to false to disable this plugin
debug = false, -- Set to true to enable debug logging
rules = {}, -- Array of rule tables (see examples above)
default = nil, -- Default theme when no rules match (nil = restore to state at init)
-- Example: { colorscheme = "oasis-lagoon", background = "dark" } | "oasis-lagoon"
keymaps = true, -- Set to false to disable, or pass a table to customize:
-- keymaps = {
-- lead_prefix = "<leader>C", -- Default prefix (default: "<leader>C")
-- keymaps = { -- Override individual keys
-- toggle = "<leader>Cc",
-- env = "<leader>Cv",
-- status = "<leader>Cs",
-- debug = "<leader>Cd",
-- reload = "<leader>Cr",
-- inspect = "<leader>Ci"
-- },
-- },
})Real-world scenarios to inspire your workflow:
π‘ Distinguish Local vs Remote Work
Instantly know when you're working on remote or mounted filesystems:
rules = {
{ path = "~/mnt/", colorscheme = "gruvbox" },
{ path = "/mnt/", colorscheme = "gruvbox" },
}π Different Themes for Different Projects
Visually separate work, personal, and client projects:
rules = {
{ path = "~/work/client-a/", colorscheme = "tokyonight" },
{ path = "~/work/client-b/", colorscheme = "nord" },
{ path = "~/personal/", colorscheme = "catppuccin" },
}π΅οΈββοΈ Warning Theme for Elevated Privileges
Use a high-visibility theme when editing as root:
local uid = (vim.uv or vim.loop).getuid()
local is_sudoedit = vim.env.SUDOEDIT == "1" -- This requires your shell's config to export a flag like: SUDO_EDITOR="env SUDOEDIT=1 /usr/bin/nvim"
local is_root = is_sudoedit or uid == 0
rules = {
{ colorscheme = "gruvbox", condition = function() return is_root end },
}ποΈ Time-Based Themes
Automatically switch between light and dark themes based on time of day.
rules = {
{
colorscheme = "catppuccin-latte",
background = "light",
condition = function()
local hour = tonumber(os.date("%H"))
return hour >= 6 and hour < 18 -- 6 AM to 6 PM
end
},
{ colorscheme = "catppuccin-mocha", background = "dark" }, -- Fallback for night
}This is just a simple time of day example using static hours.
A truly dynamic system based on actual location can be found here.
π¦ Project Type Detection
Automatically apply themes based on the type of project you're working on:
rules = {
-- Rust projects
{
colorscheme = "nord",
condition = function(cwd)
return vim.fn.filereadable(cwd .. "/Cargo.toml") == 1
end
},
-- Node.js/JavaScript projects
{
colorscheme = "catppuccin",
condition = function(cwd)
return vim.fn.filereadable(cwd .. "/package.json") == 1
end
},
-- Python projects
{
colorscheme = "tokyonight",
condition = function(cwd)
return vim.fn.filereadable(cwd .. "/pyproject.toml") == 1
or vim.fn.filereadable(cwd .. "/setup.py") == 1
end
},
}πΏ Git Branch-Based Themes
Use different themes for production branches vs development:
rules = {
{
colorscheme = "gruvbox", -- High-contrast theme for production
condition = function()
local handle = io.popen("git branch --show-current 2>/dev/null")
if handle then
local branch = handle:read("*a"):gsub("%s+", "")
handle:close()
return branch == "main" or branch == "master"
end
return false
end
},
}π Filetype-Specific Themes
Apply specific themes for configuration files or special filetypes:
rules = {
-- Match multiple filetypes with an array (recommended)
{ filetype = {"json", "yaml", "toml", "xml"}, colorscheme = "onedark" },
-- Or use individual rules
{ filetype = "json", colorscheme = "onedark" },
{ filetype = "yaml", colorscheme = "onedark" },
{ filetype = "toml", colorscheme = "onedark" },
{ filetype = "xml", colorscheme = "onedark" },
-- Or use a condition for complex logic
{
colorscheme = "onedark",
condition = function()
local ft = vim.bo.filetype
return ft == "json" or ft == "yaml" or ft == "toml" or ft == "conf"
end
},
}π Diff Mode & Read-Only Files
Use high-contrast themes when reviewing diffs or editing read-only files:
rules = {
-- High contrast for diff mode
{
colorscheme = "gruvbox",
condition = function()
return vim.wo.diff
end
},
-- Distinct theme for read-only files
{
colorscheme = "nord",
condition = function()
return vim.bo.readonly or not vim.bo.modifiable
end
},
}π» Terminal Buffer Detection
Switch themes when working with terminal buffers (or any buffer type):
rules = {
-- Simple buffer type matching
{ buftype = "terminal", colorscheme = "tokyonight" },
-- Combine with path for project-specific terminal themes
{
path = "~/work/",
buftype = "terminal",
colorscheme = "gruvbox"
},
-- Normal file buffers only (exclude special buffers)
{ buftype = "", colorscheme = "catppuccin" },
}Tip
You may also want to check out the rule recipes for submissions from users.
Inspired by my other plugin oasis.nvim which has... a lot of themes to pick from!
With so much variety, why not visually distinguish between your different working contexts in NeoVim?