WezTerm: A GPU-Accelerated Terminal Configured in Lua
WezTerm is a terminal emulator that takes the approach that your terminal should be as configurable as your text editor — using code, not a settings panel. Configuration is written in Lua, every behavior is programmable, and the multiplexer (tabs, panes, sessions) is built in rather than bolted on with tmux.
It's GPU-accelerated via wgpu, cross-platform (Linux, macOS, Windows), and handles ligatures and color profiles correctly without plugins.
Why Developers Switch to WezTerm
- Lua configuration: Full programmability, not just option flags
- Built-in multiplexer: Tabs, splits, and panes without tmux
- GPU rendering: Fast and smooth, especially with large scrollback
- Correct ligature support: Works out of the box with Fira Code, JetBrains Mono
- Multiplexer protocol: Persistent sessions over SSH
- Wide color support: True color, Sixel graphics, and image rendering
- Cross-platform: Same config file works on Linux, macOS, and Windows
Installation
# macOS (Homebrew)
brew install --cask wezterm
# Linux (AppImage)
curl -LO https://github.com/wez/wezterm/releases/latest/download/WezTerm-ubuntu22.04.AppImage
chmod +x WezTerm-ubuntu22.04.AppImage
sudo mv WezTerm-ubuntu22.04.AppImage /usr/local/bin/wezterm
# Linux (Debian/Ubuntu package)
curl -fsSL https://apt.fury.io/wez/gpg.key | sudo apt-key add -
echo "deb https://apt.fury.io/wez/ * *" | sudo tee /etc/apt/sources.list.d/wezterm.list
sudo apt update && sudo apt install wezterm
# Arch Linux
yay -S wezterm
Basic Configuration
Create ~/.config/wezterm/wezterm.lua:
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
-- Font
config.font = wezterm.font('JetBrains Mono', { weight = 'Regular' })
config.font_size = 14.0
-- Color scheme
config.color_scheme = 'Tokyo Night'
-- Window appearance
config.window_background_opacity = 0.95
config.macos_window_background_blur = 20
config.window_padding = {
left = 12, right = 12,
top = 8, bottom = 8,
}
-- Tab bar
config.enable_tab_bar = true
config.tab_bar_at_bottom = true
config.hide_tab_bar_if_only_one_tab = true
-- Scrollback
config.scrollback_lines = 10000
return config
Tabs and Panes
WezTerm's pane system is more flexible than tmux's because it's configured in Lua:
-- Default keybindings (or override them)
-- Ctrl+Shift+T = new tab
-- Ctrl+Shift+W = close tab
-- Ctrl+Shift+% = vertical split
-- Ctrl+Shift+" = horizontal split
-- Ctrl+Shift+Z = zoom pane
-- Ctrl+Shift+←↑→↓ = move between panes
-- Custom keybindings
config.keys = {
-- New tab with current directory
{
key = 't',
mods = 'CTRL|SHIFT',
action = wezterm.action.SpawnCommandInNewTab {
cwd = wezterm.home_dir,
},
},
-- Split horizontally
{
key = 'd',
mods = 'CTRL|SHIFT',
action = wezterm.action.SplitHorizontal { domain = 'CurrentPaneDomain' },
},
-- Navigate panes with vim keys
{
key = 'h', mods = 'CTRL|SHIFT',
action = wezterm.action.ActivatePaneDirection 'Left',
},
{
key = 'l', mods = 'CTRL|SHIFT',
action = wezterm.action.ActivatePaneDirection 'Right',
},
}
Leader Key (tmux-style)
If you use tmux bindings, WezTerm supports a leader key:
config.leader = { key = 'a', mods = 'CTRL', timeout_milliseconds = 1000 }
config.keys = {
-- Leader + % = vertical split
{
key = '%',
mods = 'LEADER',
action = wezterm.action.SplitHorizontal { domain = 'CurrentPaneDomain' },
},
-- Leader + " = horizontal split
{
key = '"',
mods = 'LEADER',
action = wezterm.action.SplitVertical { domain = 'CurrentPaneDomain' },
},
-- Leader + z = zoom
{
key = 'z',
mods = 'LEADER',
action = wezterm.action.TogglePaneZoomState,
},
-- Leader + c = new tab
{
key = 'c',
mods = 'LEADER',
action = wezterm.action.SpawnTab 'CurrentPaneDomain',
},
}
Status Bar with Dynamic Content
The status bar is programmatic — you can show git branch, clock, system info:
wezterm.on('update-status', function(window, pane)
-- Git branch
local cwd = pane:get_current_working_dir()
local branch = ''
if cwd then
local cmd = 'git -C ' .. cwd.file_path .. ' branch --show-current 2>/dev/null'
local handle = io.popen(cmd)
if handle then
branch = handle:read('*a'):gsub('%s+', '')
handle:close()
end
end
-- Time
local time = wezterm.strftime('%H:%M')
window:set_right_status(wezterm.format({
{ Foreground = { Color = '#a9b1d6' } },
{ Text = ' ' .. (branch ~= '' and ' ' .. branch or '') .. ' ' .. time .. ' ' },
}))
end)
Multiplexer: Persistent Remote Sessions
WezTerm has a built-in multiplexer protocol that works over SSH, keeping sessions alive:
config.unix_domains = {
{ name = 'local' },
}
config.ssh_domains = {
{
name = 'homelab',
remote_address = '192.168.1.10',
username = 'hailey',
multiplexing = 'WezTerm',
},
}
Connect to a persistent session:
wezterm connect homelab
This is different from tmux — WezTerm syncs the window layout and renders remotely, so your tabs and splits persist even if the local connection drops.
Font and Ligature Setup
config.font = wezterm.font_with_fallback({
'JetBrains Mono',
'Nerd Font Symbols',
'Noto Color Emoji',
})
-- Enable ligatures
config.harfbuzz_features = { 'calt=1', 'clig=1', 'liga=1' }
Popular fonts that work well: JetBrains Mono, Fira Code, Cascadia Code, Maple Mono.
Colorschemes
WezTerm includes 800+ built-in colorschemes:
-- List all schemes
wezterm list-fonts --list-system
-- (run in terminal)
-- Or browse at: wezfurlong.org/wezterm/colorschemes/
config.color_scheme = 'Catppuccin Mocha' -- or 'Tokyo Night', 'Gruvbox Dark', etc.
Comparison to Alacritty and Kitty
| Feature | WezTerm | Alacritty | Kitty |
|---|---|---|---|
| Config language | Lua (code) | TOML | Python (code) |
| Built-in multiplexer | ✓ | ✗ | ✓ |
| GPU acceleration | ✓ | ✓ | ✓ |
| Cross-platform | Linux/macOS/Windows | Linux/macOS/Windows | Linux/macOS |
| Image protocol | Sixel + iTerm2 | ✗ | Kitty protocol |
| Ligatures | ✓ | Experimental | ✓ |
Alacritty is the simplest and most minimal. Kitty is the most feature-complete on Linux/macOS. WezTerm is the best cross-platform option with the most programmable configuration.
Enjoyed this guide? Subscribe to DevTools Guide — a free weekly newsletter covering developer tools, workflows, and best practices.