Skip to main content

Keymaps and Leader Key

Keymaps are the customizations that make Neovim your editor. A good keymap scheme is organized, memorable, and documented.

Core Idea

The leader key is a prefix key that namespaces custom shortcuts. Vanilla Neovim defaults to \; LazyVim defaults to <Space>.

Setting the Leader Key​

~/.config/nvim/init.lua (before any plugins!)
-- MUST be set before plugins load
vim.g.mapleader = " " -- Space as leader
vim.g.maplocalleader = "\\" -- Backslash as local leader
warning

For a normal hand-written configuration, set the leader before lazy.nvim or any other plugin manager loads. The optional LazyVim profile below sets these values during LazyVim's specification setup.

Optional Custom Leader Profile​

This project includes an independently toggleable profile from common-config/nvim/plugins/custom-leader/:

; global leader
, local leader
<C-n> repeat the last f/t/F/T motion in the same direction
<C-p> repeat it in the opposite direction

Activate or inspect it from Bash:

nvim-l-on
nvim-l-status

Restart Neovim after activation. LazyVim mappings such as <leader>ff then become ;ff; <LocalLeader> mappings use ,.

Deactivate it with:

nvim-l-off
nvim-l-status

Restart Neovim after deactivation. The defaults return to <Space> as the global leader, \ as the local leader, ; as repeat-find forward, and , as repeat-find backward.

The profile does not change insert-mode completion: &lt;C-n&gt; and &lt;C-p&gt; keep their completion behavior in insert mode. It also does not modify the existing keymaps module containing jj and Alt-based scrolling.

The keymap.set API​

vim.keymap.set(mode, lhs, rhs, opts)

-- mode: "n" normal, "i" insert, "v" visual, "x" visual only,
-- "t" terminal, "c" command, "o" operator-pending
-- can be a table: {"n", "v"}
-- lhs: key combination string
-- rhs: action (string command or Lua function)
-- opts: table of options

Common Options​

{
desc = "Description", -- shows in :map and which-key
noremap = true, -- don't allow remapping (default true in keymap.set)
silent = true, -- don't echo command
expr = true, -- rhs is an expression
buffer = 0, -- buffer-local mapping (0 = current buffer)
}

Keymap Organization by Leader Prefix​

Organize keymaps into logical groups using leader prefixes:

~/.config/nvim/lua/config/keymaps.lua
local map = vim.keymap.set

-- ????????? Files ??????????????????????????????????????????????????????????????????????????????
map("n", "&lt;leader&gt;ff", "&lt;cmd&gt;Telescope find_files&lt;cr&gt;", { desc = "Find files" })
map("n", "&lt;leader&gt;fg", "&lt;cmd&gt;Telescope live_grep&lt;cr&gt;", { desc = "Find in files (grep)" })
map("n", "&lt;leader&gt;fr", "&lt;cmd&gt;Telescope oldfiles&lt;cr&gt;", { desc = "Recent files" })
map("n", "&lt;leader&gt;fw", "&lt;cmd&gt;Telescope grep_string&lt;cr&gt;", { desc = "Find word under cursor" })

-- ????????? Buffers ????????????????????????????????????????????????????????????????????????
map("n", "&lt;leader&gt;bb", "&lt;cmd&gt;Telescope buffers&lt;cr&gt;", { desc = "Switch buffer" })
map("n", "&lt;leader&gt;bd", "&lt;cmd&gt;bdelete&lt;cr&gt;", { desc = "Delete buffer" })
map("n", "&lt;leader&gt;bn", "&lt;cmd&gt;bnext&lt;cr&gt;", { desc = "Next buffer" })
map("n", "&lt;leader&gt;bp", "&lt;cmd&gt;bprev&lt;cr&gt;", { desc = "Prev buffer" })

-- ????????? Windows ????????????????????????????????????????????????????????????????????????
map("n", "&lt;leader&gt;wv", "&lt;cmd&gt;vsplit&lt;cr&gt;", { desc = "Vertical split" })
map("n", "&lt;leader&gt;ws", "&lt;cmd&gt;split&lt;cr&gt;", { desc = "Horizontal split" })
map("n", "&lt;leader&gt;wc", "&lt;cmd&gt;close&lt;cr&gt;", { desc = "Close window" })
map("n", "&lt;leader&gt;wo", "&lt;cmd&gt;only&lt;cr&gt;", { desc = "Close other windows" })

-- ????????? LSP (filled in by lsp config) ???
-- &lt;leader&gt;ld ??? LSP diagnostics
-- &lt;leader&gt;lr ??? LSP references
-- &lt;leader&gt;lR ??? LSP rename
-- &lt;leader&gt;la ??? LSP code actions
-- &lt;leader&gt;lf ??? LSP format

-- ????????? Git ????????????????????????????????????????????????????????????????????????????????????
map("n", "&lt;leader&gt;gs", "&lt;cmd&gt;Neogit&lt;cr&gt;", { desc = "Git status" })
map("n", "&lt;leader&gt;gc", "&lt;cmd&gt;Neogit commit&lt;cr&gt;", { desc = "Git commit" })
map("n", "&lt;leader&gt;gp", "&lt;cmd&gt;Neogit push&lt;cr&gt;", { desc = "Git push" })
map("n", "&lt;leader&gt;gb", "&lt;cmd&gt;Telescope git_branches&lt;cr&gt;", { desc = "Git branches" })

-- ????????? Diagnostics ????????????????????????????????????????????????????????????
map("n", "&lt;leader&gt;dl", "&lt;cmd&gt;Telescope diagnostics&lt;cr&gt;", { desc = "List diagnostics" })
map("n", "]d", vim.diagnostic.goto_next, { desc = "Next diagnostic" })
map("n", "[d", vim.diagnostic.goto_prev, { desc = "Prev diagnostic" })

-- ????????? Terminal ?????????????????????????????????????????????????????????????????????
map("n", "&lt;leader&gt;tt", "&lt;cmd&gt;terminal&lt;cr&gt;", { desc = "New terminal" })
map("t", "&lt;Esc&gt;", "&lt;C-\\&gt;&lt;C-n&gt;", { desc = "Exit terminal mode" })

-- ????????? Editor ???????????????????????????????????????????????????????????????????????????
map("n", "&lt;leader&gt;ec", "&lt;cmd&gt;e ~/.config/nvim/init.lua&lt;cr&gt;", { desc = "Edit config" })
map("n", "&lt;leader&gt;er", "&lt;cmd&gt;source ~/.config/nvim/init.lua&lt;cr&gt;", { desc = "Reload config" })

Practical Example: Scroll Shortcuts​

Here's a real-world example using Alt key combinations for comfortable scrolling without leaving the home row:

~/.config/nvim/lua/config/keymaps.lua
-- Half page scroll
vim.keymap.set('n', '<A-d>', '<C-d>', { desc = 'Scroll half page down' })
vim.keymap.set('n', '<A-u>', '<C-u>', { desc = 'Scroll half page up' })

-- Full page scroll
vim.keymap.set('n', '<A-S-d>', '<C-f>', { desc = 'Scroll full page down' })
vim.keymap.set('n', '<A-S-u>', '<C-b>', { desc = 'Scroll full page up' })

The mappings simply delegate to Neovim's built-in scroll commands, so the original Ctrl+d, Ctrl+u, Ctrl+f, and Ctrl+b motions keep working.

Terminal Compatibility

Alt key mappings require your terminal to pass Alt/Meta keys through to Neovim. Most modern terminals (Kitty, Alacritty, WezTerm, Ghostty) do this by default. Note that Neovim treats Alt+Shift+d/Alt+Shift+u as equivalent to Alt+D/Alt+U (the Shift folds into the uppercase letter), so they work on any terminal that passes Alt+D/Alt+U through.

Leader Key Map Reference​

PrefixNamespace
<leader>fFiles (find, grep, recent)
<leader>bBuffers
<leader>wWindows
<leader>lLSP actions
<leader>gGit
<leader>dDiagnostics
<leader>tTerminal
<leader>eEditor (config, reload)
<leader>sSearch/substitute
<leader>uUI (toggle options)

Which-key Plugin​

which-key.nvim shows available key bindings as you type the leader:

~/.config/nvim/lua/plugins/ui.lua
{
"folke/which-key.nvim",
event = "VeryLazy",
opts = {
plugins = { spelling = true },
},
config = function(_, opts)
local wk = require("which-key")
wk.setup(opts)
-- Register group names
wk.add({
{ "&lt;leader&gt;f", group = "Files" },
{ "&lt;leader&gt;b", group = "Buffers" },
{ "&lt;leader&gt;w", group = "Windows" },
{ "&lt;leader&gt;l", group = "LSP" },
{ "&lt;leader&gt;g", group = "Git" },
{ "&lt;leader&gt;d", group = "Diagnostics" },
{ "&lt;leader&gt;t", group = "Terminal" },
})
end,
}

Buffer-Local Keymaps​

For keymaps that only apply in specific buffers (e.g., LSP keymaps):

-- In LSP on_attach callback:
local function on_attach(client, bufnr)
local map = function(lhs, rhs, desc)
vim.keymap.set("n", lhs, rhs, { buffer = bufnr, desc = desc })
end

map("gd", vim.lsp.buf.definition, "Go to definition")
map("gr", vim.lsp.buf.references, "References")
map("K", vim.lsp.buf.hover, "Hover docs")
map("&lt;leader&gt;lR", vim.lsp.buf.rename, "Rename")
map("&lt;leader&gt;la", vim.lsp.buf.code_action, "Code action")
map("&lt;leader&gt;lf", function()
vim.lsp.buf.format({ async = true })
end, "Format")
end

In LazyVim​

LazyVim Context

LazyVim uses &lt;Space&gt; as the default leader key and pre-configures hundreds of mappings using which-key.nvim. With the optional profile enabled, press ; instead.

AspectVanilla NeovimLazyVim
Leader KeyYou define in init.luaPre-configured to <Space>
Keymap Locationlua/config/keymaps.luaAdd custom ones to lua/config/keymaps.lua
which-keyRequires manual setup & registrationBundled and pre-configured for all built-in mappings
Finding filesYou map <leader>ff to TelescopePre-mapped to <leader><space> or <leader>ff

What's Next​