Making Neovim Follow Your System Theme Automatically
Before:
" Manually switching themes throughout the day
:colorscheme gruvbox
" Hours later when it's bright outside...
:set background=light
:colorscheme solarized
After: Your Neovim automatically matches your system theme. Change your OS from dark to light mode, and watch Neovim instantly follow suit. No commands, no keybindings, just seamless theme transitions.
If you’re like me, you prefer dark themes at night and light themes during the day. Maybe you’ve set up automatic theme switching in your OS, terminal, and other apps. But what about Neovim? Having to manually toggle your editor’s theme breaks the seamless experience. Let’s fix that.
Why Automatic Theme Switching Matters
Manual theme switching is more than just an inconvenience. It’s a workflow interruption that:
- Breaks your focus when ambient lighting changes
- Creates eye strain when your editor doesn’t match other applications
- Requires remembering different colorscheme names and commands
- Feels outdated when every other app adapts automatically
The solution? Configure Neovim to detect and follow your system’s theme preference automatically.
Understanding the Components
Before diving into configuration, let’s understand what makes automatic theme switching work:
- System Theme Detection: A plugin that monitors OS theme changes
- Theme Pair: Separate colorschemes for light and dark modes
- Switch Logic: Configuration that applies the right theme at the right time
- Update Interval: How often to check for system theme changes
Setting Up Auto Theme Switching
Let’s build a configuration that automatically switches between Rose Pine (dark) and Solarized (light) based on your system theme.
Step 1: Install the Required Plugins
Using lazy.nvim, create a colorscheme.lua
file in your lua/plugins/
directory:
return {
-- Rose Pine for dark mode
{
"rose-pine/neovim",
name = "rose-pine",
priority = 1000,
opts = {
variant = "auto",
dark_variant = "main",
enable = {
terminal = true,
legacy_highlights = true,
migrations = true,
},
styles = {
transparency = false,
},
},
},
-- Solarized for light mode
{
"maxmx03/solarized.nvim",
lazy = false,
priority = 1000,
config = function()
require('solarized').setup({
transparent = {
enabled = false,
},
palette = "solarized",
styles = {
functions = { italic = true },
keywords = { italic = true },
},
})
end,
},
-- Auto theme detection
{
"f-person/auto-dark-mode.nvim",
priority = 1000,
config = function()
local auto_dark_mode = require('auto-dark-mode')
auto_dark_mode.setup({
update_interval = 1000,
set_dark_mode = function()
vim.api.nvim_set_option_value("background", "dark", {})
vim.cmd("colorscheme rose-pine")
end,
set_light_mode = function()
vim.api.nvim_set_option_value("background", "light", {})
vim.cmd("colorscheme solarized")
end,
})
end,
},
}
Step 2: Ensure Terminal Colors Support
In your options.lua
or init.lua
, make sure you have:
vim.opt.termguicolors = true
This enables 24-bit RGB color support, essential for modern colorschemes.
Step 3: Platform-Specific Considerations
The auto-dark-mode.nvim
plugin works across different platforms:
- macOS: Uses AppleScript to query system appearance
- Linux: Checks GTK theme or desktop environment settings
- Windows: Queries Windows registry for theme preference
No additional configuration needed - the plugin handles platform detection automatically.
Customizing Your Setup
Different Theme Combinations
Not a fan of Rose Pine and Solarized? Swap them out:
-- Gruvbox dark, One light
set_dark_mode = function()
vim.api.nvim_set_option_value("background", "dark", {})
vim.cmd("colorscheme gruvbox")
end,
set_light_mode = function()
vim.api.nvim_set_option_value("background", "light", {})
vim.cmd("colorscheme one")
end,
Adjusting Check Frequency
The update_interval
is in milliseconds. For less frequent checks:
update_interval = 5000, -- Check every 5 seconds
Lower values mean more responsive switching but slightly higher CPU usage.
Adding Theme Switch Hooks
Want to run commands when themes change? Add callbacks:
set_dark_mode = function()
vim.api.nvim_set_option_value("background", "dark", {})
vim.cmd("colorscheme rose-pine")
-- Additional dark mode settings
vim.opt.cursorline = true
require('lualine').setup({ options = { theme = 'rose-pine' }})
end,
Troubleshooting Common Issues
Theme Not Switching
Check if the plugin can detect your system theme:
:lua print(require('auto-dark-mode').get_mode())
Should return “dark” or “light”. If it returns nil, your system might need additional configuration.
Delayed Switching
If theme changes feel sluggish:
- Reduce
update_interval
to 500-1000ms - Check for heavy autocommands that might delay updates
- Ensure your colorschemes load quickly (avoid heavy computations)
Colors Look Wrong
Verify terminal support:
:echo $TERM
:echo &termguicolors
:echo has('termguicolors')
All should indicate proper color support.
Linux-Specific Issues
On Linux, the plugin tries multiple detection methods. If it’s not working:
- Check your desktop environment is supported
- Try setting the
GTK_THEME
environment variable - Consider using a systemd user service to set theme variables
Performance Considerations
Automatic theme switching has minimal performance impact:
- Theme detection uses native OS APIs (very fast)
- Checks run asynchronously without blocking Neovim
- Colorscheme changes only trigger when themes actually switch
- Memory usage is negligible (< 1MB)
For battery-conscious users, the default 1-second interval is a good balance. The actual CPU usage is typically < 0.1%.
Advanced Configuration
Manual Override
Sometimes you want to temporarily override automatic switching:
vim.keymap.set('n', '<leader>td', function()
require('auto-dark-mode').disable()
vim.cmd('colorscheme rose-pine')
end, { desc = 'Force dark theme' })
vim.keymap.set('n', '<leader>tl', function()
require('auto-dark-mode').disable()
vim.cmd('colorscheme solarized')
end, { desc = 'Force light theme' })
vim.keymap.set('n', '<leader>ta', function()
require('auto-dark-mode').enable()
end, { desc = 'Auto theme' })
Status Line Integration
Show current theme in your status line:
-- For lualine
{
function()
local mode = require('auto-dark-mode').get_mode()
return mode == 'dark' and '🌙' or '☀️'
end,
}
Project-Specific Themes
Override themes for specific projects:
vim.api.nvim_create_autocmd("DirChanged", {
callback = function()
local cwd = vim.fn.getcwd()
if cwd:match("work%-projects") then
-- Always use light theme for work projects
require('auto-dark-mode').disable()
vim.cmd('colorscheme solarized')
else
require('auto-dark-mode').enable()
end
end,
})
Taking It Further
Once you have automatic theme switching working:
- Sync with tmux: Use tmux-dark-notify or similar for consistent terminal multiplexer themes
- Match syntax plugins: Configure TreeSitter, LSP, and other plugins to respect theme changes
- Create custom themes: Build your own matched dark/light theme pair
- Time-based switching: Combine with solar time calculations for sunset/sunrise switching
Conclusion
Automatic theme switching transforms Neovim from a static editor into a dynamic environment that adapts to your needs. With just a few lines of configuration, you get a seamless experience that matches your system preferences without any manual intervention.
Your eyes will thank you, your workflow will be smoother, and you’ll wonder how you ever lived without it. Set it up once, and enjoy perfectly matched themes forever.