Programmatically switch dark mode in MacOS

Like most developers, I prefer dark mode in my terminal and code editor.


🌺 Spring is coming around however, and that means, somewhere between 3 and 4 o’clock in the afternoon, the sun is starting to produce a nasty glare on my monitor making dark mode an unacceptable choice.

I threw some code at this problem and now I can quickly switch between light and dark mode from the command line.

Let’s approach this from the outside-in. This article will describe how to synchronize your theme across MacOS, Tmux and Vim.

The groundwork

I’m using the well-known Solarized theme, which is especially great in this case because it has a light and dark variant.

Screenshot showing Solarized in both light and dark modes.

I use iTerm2 and I installed the official Solarized dark iTerm2 theme. It actually doesn’t matter that much since I’m running Tmux, which will have its own color handling and is overlaid on your terminal.

Tmux

In Tmux I’m using two custom themes, a dark one and a light one. Normally I would not recommend customly theming anything unless you really like minutiae. However, there are not a lot of interface elements in Tmux, so you can quickly get a nice result without too much configuration. Just make sure your colors match the Vim theme. You can find the files in my dotfiles repository.

In my tmux.conf file I include the dark theme by default:

source-file ~/.tmux-themes/dark.conf

One very important line is at the bottom of both my theme files:

setenv -g TMUX_THEME dark

This sets an environment variable containing the current theme, which we’ll get back to in the Vim section.

Vim

In Vim I’m using this variation of Solarized which is optimized for terminal Vim.

I’m using these settings to ensure proper color handling:

let g:solarized_termcolors=256
colorscheme solarized8

Switching theme from the command line

I’ve created two bash files, light.sh and dark.sh, to switch theme from the command line.
They look like this:

# light.sh
tmux source-file ~/.tmux-themes/light.conf
osascript -l JavaScript -e "Application('System Events').appearancePreferences.darkMode = false"
# dark.sh
tmux source-file ~/.tmux-themes/dark.conf
osascript -l JavaScript -e "Application('System Events').appearancePreferences.darkMode = true"

The first line of each file sources the right theme file.
The second line executes a bit of JavaScript via osascript, which tells MacOS to toggle dark mode.

Awesome! Now we can very quickly switch Tmux theme, and the MacOS style will follow suit.

Switching theme is a matter of running

$ sh ~/light.sh

💡 Alternatively, you can put this in a function. I’m using Fish shell, and actually have two Fish functions that call the above sh command. Having the scripts in separate files worked best for me when integrating with Vim, due to PATH issues.

Syncing Vim

We’re not done: when starting a new Vim session, it will just use whatever your default background setting is, probably dark.
To fix this, we’re going to listen to that environment variable we configured above:

function! Chomp(string)
    return substitute(a:string, '\n\+$', '', '')
endfunction

let tmuxtheme = split(Chomp(system('tmux show-environment -g TMUX_THEME')), "=")[1]
if tmuxtheme == "dark"
    set background=dark
else
    set background=light
end

This bit of code should go in your .vimrc file. It parses the current TMUX_THEME environment variable, and switches background accordingly.

Neat! Now new Vim sessions will follow along with our current preference.

Switching theme from Vim

Last but not least, we need a way to toggle the theme from within Vim.

Add the following functions to your .vimrc:

function! Dark()
	set background=dark
    silent !sh ~/path/to/dark.sh
endfunction

function! Light()
	set background=light
    silent !sh ~/path/to/light.sh
endfunction

command! Dark call Dark()
command! Light call Light()

You can now call :Dark and :Light from within Vim to switch theme, and actually have MacOS and Tmux follow right along.

Anything else?

One point of all this is to stop the jarring effect of switching to a white screen after working in a dark terminal for a while. Luckily a lot of software nowadays contains an option for switching theme automatically when the OS preference changes. Slack and Brave Browser for instance both respect this system setting. This really helps, so make sure to use that option.

Since a lot of my tools these days live in the browser, and websites can actually implement CSS based on the system setting, you might get lucky. It depends on the sites you frequent. DuckDuckGo, Twitter, and this blog will fall in line neatly.

One improvement that I’m still thinking about, is to change the theme in running Vim sessions. I usually work using a dozen Tmux sessions for different projects, containing their own Vim processes. A running Vim process won’t re-evaluate their colorscheme when I change the system setting from elsewhere.

Is there a way to get a list of running Vim commands, and passing a command to them? I don’t know.

Might be a fun trick. On the other hand…

XKCD comic showing a diagram in which automating a task ends up taking more time than doing the task.

Credit: XKCD