Navigate Neovim splits and multiplexer panes with the same keys.
Built for people switching between multiplexers who don't want to reinstall a different navigation plugin each time. There's also a bg feature that keeps your terminal background in sync with your colorscheme, and more may follow as the need arises.
If you're settled on one multiplexer, the dedicated plugins might suit you better:
- tmux: vim-tmux-navigator
- Zellij: vim-zellij-navigator
- Neovim >= 0.10
{
"nichtj3/mux.nvim",
opts = {},
}opts = {
nav = {
enabled = true,
keys = {
left = "<C-h>", -- defaults, override to taste
down = "<C-j>",
up = "<C-k>",
right = "<C-l>",
},
},
bg = { enabled = false }, -- keeps the terminal background in sync with your colorscheme
}Install via TPM:
set -g @plugin 'nichtj3/mux.nvim'Override keys before the plugin line (defaults match nav.keys):
set -g @mux_mapping_left "C-h"
set -g @mux_mapping_down "C-j"
set -g @mux_mapping_up "C-k"
set -g @mux_mapping_right "C-l"On the tmux side, each key binding checks whether Neovim is the foreground process. If it is, the key passes through to Neovim; if not, tmux moves pane focus directly. Same keys, both contexts.
Follow setup instructions for vim-zellij-navigator. It handles Zellij-side routing: when Neovim is focused, keys pass through; otherwise it moves pane focus directly. mux.nvim calls zellij action move-focus when Neovim hits a split boundary.
herdr plugin install nichtj3/mux.nvimThen bind the actions in ~/.config/herdr/config.toml:
[[keys.command]]
key = "ctrl+h"
type = "plugin_action"
command = "mux.nvim.left"
[[keys.command]]
key = "ctrl+j"
type = "plugin_action"
command = "mux.nvim.down"
[[keys.command]]
key = "ctrl+k"
type = "plugin_action"
command = "mux.nvim.up"
[[keys.command]]
key = "ctrl+l"
type = "plugin_action"
command = "mux.nvim.right"Note
Neovim detection requires jq. Without it, the actions always move pane focus.
The adaptor covers the Neovim side: detect() returns true when the multiplexer is active, and focus(dir) moves the pane when Neovim hits a split boundary. You're responsible for the mux side — intercept the keys and route them as tmux and herdr do above.
Pass a table directly to use a one-off adaptor:
opts = {
adaptor = {
proc_name = "mymux",
detect = function() return vim.env.MYMUX ~= nil end,
focus = function(dir)
vim.fn.jobstart({ "mymux", "focus", dir }, { detach = true })
end,
},
}Or register one for auto-detection alongside the built-ins:
require("mux.adaptor").register("mymux", {
proc_name = "mymux",
detect = function() return vim.env.MYMUX ~= nil end,
focus = function(dir)
vim.fn.jobstart({ "mymux", "focus", dir }, { detach = true })
end,
})Force a built-in by name:
opts = { adaptor = "tmux" }