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 (default: \, recommended: <Space>) that namespaces your custom shortcuts to avoid conflicts with Neovim's built-in keys.

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

The leader key MUST be set before lazy.nvim or any other plugin manager loads. Set it at the very top of init.lua or before require("plugins").

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", "<leader>ff", "<cmd>Telescope find_files<cr>", { desc = "Find files" })
map("n", "<leader>fg", "<cmd>Telescope live_grep<cr>", { desc = "Find in files (grep)" })
map("n", "<leader>fr", "<cmd>Telescope oldfiles<cr>", { desc = "Recent files" })
map("n", "<leader>fw", "<cmd>Telescope grep_string<cr>", { desc = "Find word under cursor" })

-- ─── Buffers ────────────────────────
map("n", "<leader>bb", "<cmd>Telescope buffers<cr>", { desc = "Switch buffer" })
map("n", "<leader>bd", "<cmd>bdelete<cr>", { desc = "Delete buffer" })
map("n", "<leader>bn", "<cmd>bnext<cr>", { desc = "Next buffer" })
map("n", "<leader>bp", "<cmd>bprev<cr>", { desc = "Prev buffer" })

-- ─── Windows ────────────────────────
map("n", "<leader>wv", "<cmd>vsplit<cr>", { desc = "Vertical split" })
map("n", "<leader>ws", "<cmd>split<cr>", { desc = "Horizontal split" })
map("n", "<leader>wc", "<cmd>close<cr>", { desc = "Close window" })
map("n", "<leader>wo", "<cmd>only<cr>", { desc = "Close other windows" })

-- ─── LSP (filled in by lsp config) ─
-- <leader>ld → LSP diagnostics
-- <leader>lr → LSP references
-- <leader>lR → LSP rename
-- <leader>la → LSP code actions
-- <leader>lf → LSP format

-- ─── Git ────────────────────────────
map("n", "<leader>gs", "<cmd>Neogit<cr>", { desc = "Git status" })
map("n", "<leader>gc", "<cmd>Neogit commit<cr>", { desc = "Git commit" })
map("n", "<leader>gp", "<cmd>Neogit push<cr>", { desc = "Git push" })
map("n", "<leader>gb", "<cmd>Telescope git_branches<cr>", { desc = "Git branches" })

-- ─── Diagnostics ────────────────────
map("n", "<leader>dl", "<cmd>Telescope diagnostics<cr>", { 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", "<leader>tt", "<cmd>terminal<cr>", { desc = "New terminal" })
map("t", "<Esc>", "<C-\\><C-n>", { desc = "Exit terminal mode" })

-- ─── Editor ─────────────────────────
map("n", "<leader>ec", "<cmd>e ~/.config/nvim/init.lua<cr>", { desc = "Edit config" })
map("n", "<leader>er", "<cmd>source ~/.config/nvim/init.lua<cr>", { desc = "Reload config" })

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({
{ "<leader>f", group = "Files" },
{ "<leader>b", group = "Buffers" },
{ "<leader>w", group = "Windows" },
{ "<leader>l", group = "LSP" },
{ "<leader>g", group = "Git" },
{ "<leader>d", group = "Diagnostics" },
{ "<leader>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("<leader>lR", vim.lsp.buf.rename, "Rename")
map("<leader>la", vim.lsp.buf.code_action, "Code action")
map("<leader>lf", function()
vim.lsp.buf.format({ async = true })
end, "Format")
end

What's Next