2024-12-06 • 2 min

How to Sync Neovim's Color Scheme with OS Dark Mode

Neovim updating its color scheme in sync with macOS

We’ll take advantage of Neovim’s sockets support to run a Lua function that updates the color scheme. Unfortunately, this guide doesn’t apply to vanilla Vim since it doesn’t support sockets at the moment.

If your Neovim config is written in Vimscript, you can embed a Lua function like this:

" vimscript code
lua << EOF
   print("This is lua code")
EOF
" more vimscript code

Create the Socket Files

The following function creates a unique socked file for each Neovim process in /tmp/nvim and makes the instance listen on it:

function createSocket()
  pid = vim.fn.getpid()
  socket_name = '/tmp/nvim/nvim' .. pid .. '.sock'
  vim.fn.mkdir('/tmp/nvim', 'p')
  vim.fn.serverstart(socket_name)
end

Neovim will delete the socket files automatically when you close it.

Update the Color Scheme

Now we’ll write another function that reads the current OS theme and updates Neovim’s color scheme accordingly.

macOS

function updateColorscheme()
  exit_code = os.execute("defaults read -g AppleInterfaceStyle")
  if exit_code == 0 then
    -- Set dark color scheme
  else
    -- Set light color scheme
  end
end

Linux

function updateColorscheme()
  command = "dbus-send --session --dest=org.freedesktop.portal.Desktop --print-reply /org/freedesktop/portal/desktop org.freedesktop.portal.Settings.Read string:'org.freedesktop.appearance' string:'color-scheme' | grep -o 'uint32 .' | cut -d' ' -f2"

  handle = io.popen(command)
  output = handle:read("*a")
  handle:close()
  output = string.gsub(output, "\n", "")

  if output == "1" then
    -- Set dark color scheme
  else
    -- Set light color scheme
  end
end

Call the Functions on Startup

Both functions we’ve created must run automatically when we launch a Neovim instance. We can achieve this with a custom augroup:

vim.api.nvim_create_augroup('custom_startup', {})

vim.api.nvim_create_autocmd('VimEnter', {
  desc = 'Create a socket for every nvim process',
  group = 'custom_startup',
  once = true,
  callback = createSocket
})

vim.api.nvim_create_autocmd('UIEnter', {
  desc = 'Set the appropriate theme on startup',
  group = 'custom_startup',
  once = true,
  callback = updateColorscheme
})

React to OS Theme Changes

We’ll use pynvim to connect to each of the socket files and call our updateColorscheme function. You can install pynvim through Python’s pip.

Create a script with the following contents:

#!/usr/bin/env python3

import glob
from pynvim import attach

nvim_sockets = (attach('socket', path=p) for p in glob.glob('/tmp/nvim/nvim*.sock'))

for nvim in nvim_sockets:
    nvim.exec_lua('updateColorscheme()')

All that remains is to hook this Python script into your OS theme change event so it runs automatically every time. This is not so straightforward and will depend on your platform:

  • On macOS, you can use my abysswatcher daemon, which I created specifically for this purpose. You can find instructions on how to set it up in the README file.
  • On Linux, if you’re using Gnome, you can use the Night Theme Switcher extension. Otherwise, you’re on your own, but you’ll figure it out 😉.

Although this guide was focused on Neovim, you can of course use this same approach to automate other things. For example, I use abysswatcher to update themes in Neovim, the Fish shell and other utilities like bat.

Back to blog

© 2025 Diego Otero