Color scheme switching for WezTerm February 15, 2025
I really like WezTerm, a cross-platform terminal emulator and multiplexer I have been using for some time now. What particularly drew me to it was the combination of
- cross-platform support,
- tabbing,
- support for panes,
- ligatures, and
- powerful customization with Lua.
WezTerm also supports color schemes, and I use one based on the beautiful
Selenized color palette, in
particular Selenized black & white.
While WezTerm comes with a lot of color schemes,
at the time of writing, support for Selenized Black and Selenized White is only
present out of the box in the nightly builds (via Gogh).
This is why I wrote my own color scheme definitions and put them in
~/config/wezterm-color-schemes
so they can be picked up by WezTerm.
Here they are.
Selenized black & white
Selenized Black.toml
:
[colors]
ansi = [
"#252525",
"#ed4a46",
"#70b433",
"#dbb32d",
"#368aeb",
"#eb6eb7",
"#3fc5b7",
"#777777"
]
brights = [
"#3b3b3b",
"#ff5e56",
"#83c746",
"#efc541",
"#4f9cfe",
"#ff81ca",
"#56d8c9",
"#dedede"
]
background = "#181818"
foreground = "#b9b9b9"
cursor_bg = "#83c746"
cursor_border = "#83c746"
cursor_fg = "#181818"
selection_bg = "#b9b9b9"
selection_fg = "#181818"
[colors.indexed]
[metadata]
aliases = []
name = "Selenized Black"
Selenized White.toml
:
[colors]
ansi = [
"#ebebeb",
"#d6000c",
"#1d9700",
"#c49700",
"#0064e4",
"#dd0f9d",
"#00ad9c",
"#878787"
]
brights = [
"#cdcdcd",
"#bf0000",
"#008400",
"#af8500",
"#0054cf",
"#c7008b",
"#009a8a",
"#282828"
]
background = "#ffffff"
foreground = "#474747"
cursor_bg = "#008400"
cursor_border = "#008400"
cursor_fg = "#ffffff"
selection_bg = "#474747"
selection_fg = "#ffffff"
[colors.indexed]
[metadata]
aliases = []
name = "Selenized White"
Switching between color schemes
This is all well and good. However, another thing WezTerm is missing out of the box is an easy way to switch color schemes. I mostly use dark colors, but sometimes I want to put a screenshot of my terminal in a document and want it to look bright, or I am sharing my screen with someone who can read better when the text is dark on light, so having a convenient way to switch betwwen dark and light modes is useful to me.
Fortunately, WezTerm is highly configurable, so I was able to implement the toggle myself. Here is a minimal, but heavily commented, WezTerm configuration that shows my solution. I have included links to the relevant WezTerm documentation so you can read up on what each of the parts does if you wish.
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
Look up home directory to construct some paths (this is standard Lua):
local home = os.getenv('HOME')
WezTerm will look for color scheme files in the directories in
color_scheme_dirs
:
config.color_scheme_dirs = { home .. '/config/wezterm-color-schemes' }
We will persist the name of the current color scheme in ~/.color-scheme
:
local color_scheme_file = home .. '/.color-scheme'
We will toggle between these two color schemes:
local default_color_scheme = 'Selenized Black'
local alternate_color_scheme = 'Selenized White'
In my case, these are the custom schemes described above, but they don't have to be. You can name any color scheme WezTerm is able to find, custom or built in.
Function to load the name of the current color scheme,
falls back to default_color_scheme
if the file is not present:
local function load_color_scheme()
local color_scheme = default_color_scheme
local file = io.open(color_scheme_file, 'r')
if file then
color_scheme = file:read('*a')
file:close()
end
return color_scheme
end
Function to save the name of the current color scheme:
local function save_color_scheme(color_scheme)
local file = io.open(color_scheme_file, 'w+')
if file then
file:write(color_scheme)
file:flush()
file:close()
end
return color_scheme
end
Make WezTerm use the saved color scheme by assigning it to
color_scheme
:
config.color_scheme = load_color_scheme()
This is an event handler for the custom event
toggle-color-scheme
.
It determines which scheme to switch to and saves it.
wezterm.on('toggle-color-scheme', function(_window, _pane)
local color_scheme = load_color_scheme()
local new_color_scheme = default_color_scheme
if color_scheme == default_color_scheme then
new_color_scheme = alternate_color_scheme
end
save_color_scheme(new_color_scheme)
end)
I use Ctrl-b
as the leader key,
i. e., as a prefix for my custom keyboard shortcuts:
config.leader = { key = 'b', mods = 'CTRL', timeout_milliseconds = 1000 }
Now we configure the key binding
to toggle the color scheme.
Specifying both LEADER
and CTRL
makes it so I have to press the
leader (Ctrl-b
), followed by Ctrl-c
.
config.keys = {
{
key = 'c',
mods = 'LEADER|CTRL',
Use Multiple
to trigger two actions when the key combination is pressed:
action = wezterm.action.Multiple {
First action: fire the event that will save the new color scheme:
wezterm.action.EmitEvent 'toggle-color-scheme',
Second action: make WezTerm reload its configuration, thus activating the new color scheme:
wezterm.action.ReloadConfiguration,
},
},
}
Finally, return the configuration to WezTerm:
return config
And here is the entire thing:
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
local home = os.getenv('HOME')
config.color_scheme_dirs = { home .. '/config/wezterm-color-schemes' }
local color_scheme_file = home .. '/.color-scheme'
local default_color_scheme = 'Selenized Black'
local alternate_color_scheme = 'Selenized White'
local function load_color_scheme()
local color_scheme = default_color_scheme
local file = io.open(color_scheme_file, 'r')
if file then
color_scheme = file:read('*a')
file:close()
end
return color_scheme
end
local function save_color_scheme(color_scheme)
local file = io.open(color_scheme_file, 'w+')
if file then
file:write(color_scheme)
file:flush()
file:close()
end
return color_scheme
end
config.color_scheme = load_color_scheme()
wezterm.on('toggle-color-scheme', function(_window, _pane)
local color_scheme = load_color_scheme()
local new_color_scheme = default_color_scheme
if color_scheme == default_color_scheme then
new_color_scheme = alternate_color_scheme
end
save_color_scheme(new_color_scheme)
end)
config.leader = { key = 'b', mods = 'CTRL', timeout_milliseconds = 1000 }
config.keys = {
{
key = 'c',
mods = 'LEADER|CTRL',
action = wezterm.action.Multiple {
wezterm.action.EmitEvent 'toggle-color-scheme',
wezterm.action.ReloadConfiguration,
},
},
}
return config
Saving this to your home directory as .wezterm.lua
sets you up to toggle between
light and dark mode by pressing Ctrl-b Ctrl-c
.
Using this combination of persisting the color scheme and config reload means that the change applies to all open WezTerm panes and windows (because they all belong to the same WezTerm instance).
Switching the current pane only
I also wrote a script to change the color scheme for the current pane, but this one only works for my custom color schemes because it reads the TOML files and outputs the escape sequences to change the terminal color palette. The script expects to live in the same directory as the TOML files and requires toml-cli to work. It does not toggle between two color schemes, but expects a color scheme name as an argument. Here it is:
#!/usr/bin/zsh
if [ -z "$1" -o -n "$2" ]; then
echo "Usage: $0 COLOR_SCHEME_NAME" >&2
exit 1
fi
SCHEME="$1"
HERE="$(dirname "$0")"
TOML="$HERE/$SCHEME.toml"
if ! [ -f "$TOML" ]; then
echo "Color scheme not found at $TOML" >&2
exit 1
fi
function color() {
case "$1" in
0|1|2|3|4|5|6|7) toml get -r "$TOML" "colors.ansi[$1]" ;;
8|9|10|11|12|13|14|15) toml get -r "$TOML" "colors.brights[$(($1-8))]" ;;
*) toml get -r "$TOML" "colors.$1" ;;
esac
}
echo -en "\\e]4;0;`color 0`;1;`color 1`;2;`color 2`;3;`color 3`;4;`color 4`;5;`color 5`;6;`color 6`;7;`color 7`;8;`color 8`;9;`color 9`;10;`color 10`;11;`color 11`;12;`color 12`;13;`color 13`;14;`color 14`;15;`color 15`\\a"
echo -en "\\e]10;`color foreground`;`color background`;`color cursor_bg`\\a"
echo -en "\\e]17;`color selection_bg`\\a"
echo -en "\\e]19;`color selection_fg`\\a"
It have not yet created a key binding to invoke this script, but it should be possible with a bit of Lua code (emit a custom event and handle it by executing the script in a subprocess, making sure the output goes to the correct pane).
Where to now?
For now, I am happy with my setup. I can toggle between dark and light modes with a keybinding. Possible extensions I have thought about, but not yet implemented are:
- toggling the current pane via a key binding,
- cover more color options with the color schemes (e. g., tab bar colors),
- integrate color schemes into the launcher menu.