The Emacs Keybinding System, Explained for People Who Keep Getting Lost

2024/03/15

BUILDemacs🍞

I’ve been using Doom Emacs for months and I still couldn’t explain why C-x and C-c and SPC all exist as separate things. Every time I hit a keybinding that didn’t work, I’d stare at the screen, try three variations, google it, paste the first StackOverflow answer into my config, and move on.

The information exists. It’s scattered across the Emacs manual (which reads like a legal document from 1985), the Doom Emacs discourse, Evil mode docs, and a dozen “getting started with Emacs” posts that each cover 20% of the picture.

This is the post I wish existed when I started. The full conceptual model — why there are multiple keybinding layers, which one wins when they conflict, and the patterns you’ll actually use daily.

Why so many layers?

Emacs is 40+ years old. It has accumulated keybinding systems the way an old house accumulates electrical panels. Each one made sense at the time. Together, they form something that looks like chaos but actually has a logic to it.

The short version: Emacs has multiple keybinding layers because it has multiple scopes of behavior. Some keys should work everywhere (save, quit, open file). Some should only work in specific contexts (run Python, fold Org headings). Some are user-defined. Some are from packages. They all need to coexist without stepping on each other.

That’s the architecture. Now let’s look at the actual layers.

The three keymap levels

Every keybinding in Emacs lives in a keymap — a data structure that maps key sequences to commands. There are three levels, and they have a strict precedence:

Level Keymap Precedence Scope
1 Minor mode maps Highest Active minor modes (multiple can coexist)
2 Major mode map Middle The current major mode (one per buffer)
3 Global map Lowest Always active, everywhere

When you press a key, Emacs checks minor mode maps first, then the major mode map, then the global map. First match wins.

This is the single most important thing to understand. If a keybinding “doesn’t work,” it’s almost always because something higher in the precedence chain is intercepting it. A minor mode is eating your keypress before the major mode ever sees it.

You can inspect this yourself: SPC h v (or C-h v in vanilla Emacs) and look up any keymap variable. SPC h k will tell you what a specific key sequence does and which keymap it came from.

The five leader key prefixes

If keymaps are the mechanism, leader keys are the interface. They’re the prefix keys that organize commands into discoverable groups. Emacs has accumulated five of them:

C-x — The original

This is vanilla Emacs’s main prefix. It’s been around since the beginning. Global operations that work regardless of what mode you’re in.

Whether you’re in Doom, Spacemacs, or bare Emacs — these work. They’re the bedrock.

C-c — Mode-specific + user custom

C-c is reserved for two things: mode-specific commands and user-defined keybindings. The Emacs convention is that C-c followed by a letter (C-c a, C-c b, etc.) belongs to the user. C-c followed by a control character or other prefix belongs to the mode.

In practice:

The commands change depending on your major mode. That’s the whole point — C-c is the prefix that says “what mode am I in right now, and what can I do here?”

If you’re defining your own keybindings, C-c [letter] is the conventionally safe namespace. Packages aren’t supposed to bind there (though some do anyway, because conventions are suggestions).

SPC — Doom’s central dispatch

This is the Doom Emacs innovation. SPC (Space) as a leader key, only active in Evil normal/visual mode (not insert mode). It’s a centralized command hub that organizes almost everything into a mnemonic tree:

Prefix Category Examples
SPC f Files SPC f f find file, SPC f s save
SPC b Buffers SPC b b switch, SPC b k kill
SPC w Windows SPC w v split vertical, SPC w w switch
SPC p Projects SPC p p switch project, SPC p f find file in project
SPC g Git SPC g g Magit status, SPC g b blame
SPC s Search SPC s p search project, SPC s s search buffer
SPC o Open SPC o t terminal, SPC o p treemacs
SPC h Help SPC h k describe key, SPC h v describe variable
SPC m Major mode Context-dependent — see below

SPC m is particularly clever. It’s the equivalent of C-c but routed through the leader key system. In Org mode, SPC m shows Org commands. In Python mode, it shows Python commands. Same idea as C-c, different ergonomics.

Important: SPC only works in Evil command mode (normal/visual). If you’re in insert mode, you need to hit ESC (or jk or whatever your escape binding is) first. This trips everyone up for the first week.

M-x — The universal escape hatch

M-x (Meta + x, usually Alt+x or Option+x on Mac) opens the command palette. Type any command name, fuzzy-match it, execute it. It’s not really a “leader key” in the traditional sense — it’s a search interface over every command Emacs knows about.

When you can’t remember the keybinding, M-x is your friend. In Doom, SPC : does the same thing.

This is also how you discover commands. Type a keyword, see what comes up. Half my keybinding knowledge came from M-x completions.

M-g — Jump commands

A lesser-known vanilla Emacs prefix for navigation/goto commands:

Most Doom users never touch this because SPC covers the same territory with better discoverability. But if you’re in insert mode and need to jump to a line, M-g g still works.

How modes actually work

I mentioned “major mode” and “minor mode” above without explaining them. These are core Emacs concepts that control everything about how a buffer behaves.

Major mode: Every buffer has exactly one. It determines the fundamental editing behavior — syntax highlighting, indentation rules, available commands. Open a .py file and you’re in python-mode. Open a .org file and you’re in org-mode. Open a plain text file and you’re in text-mode. You can see the current major mode in the modeline at the bottom of the screen.

Minor mode: A buffer can have many of these simultaneously. They add functionality on top of the major mode. flycheck-mode gives you real-time syntax checking. visual-line-mode wraps long lines. evil-mode itself is a minor mode — it layers Vim keybindings on top of everything.

This is why minor mode keybindings have the highest precedence. Evil mode needs its j/k/h/l to override whatever the major mode might have bound to those keys. The precedence system makes this work without conflicts.

Switch major mode manually: M-x python-mode, M-x org-mode, etc. Toggle a minor mode: M-x flycheck-mode (it’s a toggle — call it again to turn it off). Emacs usually picks the right major mode from the file extension, but sometimes you need to override.

Evil mode naming conventions

If you’re using Doom, you’re using Evil mode (Vim keybindings in Emacs). The codebase has naming conventions that help you navigate:

Pattern Meaning Example
evil-xxx Core Evil mode function/variable evil-delete, evil-normal-state
+evil/xxx Doom module extension to Evil +evil/window-move-right
+evil:xxx Less common Doom convention (user/config) Various custom commands
+fold/xxx Doom’s code folding extensions +fold/toggle, +fold/open-all

The + prefix is Doom’s convention for “this is a Doom module addition, not core Evil.” The / separates namespace from function name. When you see +evil/ in a keybinding description, you know it’s Doom-specific, not standard Evil.

Keybindings you’ll actually use daily

Theory is great. Here’s what I actually type.

Keys What it does Context
SPC f f Open file Anywhere
SPC f s Save current file Anywhere
SPC b b Switch buffer Anywhere
SPC b k Kill buffer Anywhere
SPC p p Switch project Anywhere
SPC p f Find file in project Anywhere
SPC s p Search in project (ripgrep) Anywhere
SPC o p / SPC o P Treemacs / Treemacs at current file Anywhere

Windows

Keys What it does
SPC w v Split vertical
SPC w s Split horizontal
SPC w w Cycle to next window
SPC w q Close window
SPC w = Balance window sizes

Evil editing (Vim-style)

This is where Doom really shines — you get the full Vim editing language inside Emacs.

Keys What it does Notes
C-w Delete word backward In insert mode (Doom binds this to evil-delete-backward-word)
viw then p Replace word with clipboard Select inner word, paste over it
viw then gp Replace word without overwriting clipboard Same as above, but your yank register stays intact
yiw Yank (copy) word under cursor Then viwp elsewhere to replace
"0p Paste from yank register When default register got overwritten by a delete

That last one — "0p — is a lifesaver. Here’s the scenario: you yank a word with yiw. Then you diw to delete another word. Now your default register has the deleted word, not the one you yanked. But the yank register ("0) still has your original yank. "0p pastes it.

Git (Magit)

Keys What it does
SPC g g Magit status (the main interface)
SPC g b Git blame
SPC g l Git log

Help (your debugging toolkit)

Keys What it does When to use
SPC h k Describe key — what does this key do? “What did I just press?”
SPC h v Describe variable — inspect keymaps, configs “What’s the value of X?”
SPC h f Describe function “What does this command actually do?”
SPC h b Describe bindings — show ALL keybindings “What’s available in this mode?”
SPC h d m Doom module docs “How does this Doom module work?”

How to set your own keybindings

Three main approaches, from Doom-specific to vanilla:

map! (Doom’s macro) — the recommended way in Doom:

;; in ~/.doom.d/config.el
(map! :leader
      :desc "Open scratch" "x" #'doom/open-scratch-buffer)

(map! :after org
      :map org-mode-map
      :n "C-c t" #'org-todo)

define-key — standard Emacs, works everywhere:

(define-key global-map (kbd "C-c k") #'kill-whole-line)

global-set-key — shorthand for global-map bindings:

(global-set-key (kbd "C-c k") #'kill-whole-line)

In Doom, prefer map! — it understands Evil states (:n for normal, :i for insert, :v for visual) and integrates with which-key for discoverability.

Debugging keybinding problems

When a key doesn’t do what you expect:

  1. SPC h k then press the key — tells you exactly what command it runs and which keymap defined it
  2. SPC h v [keymap-name] — inspect the raw keymap variable (e.g., org-mode-map)
  3. M-x describe-mode — shows all active modes and their keybindings in the current buffer
  4. Check precedence — minor mode > major mode > global. If something unexpected is happening, a minor mode is probably intercepting

The most common issue: you defined a binding in the global map, but a minor mode is overriding it. The fix is to bind it in the minor mode map instead, or use map! with the appropriate Evil state to ensure it takes priority.

The mental model

Here’s the picture I wish someone had drawn for me:

┌─────────────────────────────────────────┐
│           Minor mode maps               │  ← Checked first (Evil, flycheck, etc.)
│  ┌───────────────────────────────────┐  │
│  │       Major mode map              │  │  ← Checked second (python-mode, org-mode)
│  │  ┌─────────────────────────────┐  │  │
│  │  │       Global map            │  │  │  ← Checked last (C-x, universal bindings)
│  │  │                             │  │  │
│  │  └─────────────────────────────┘  │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Leader keys are interfaces into these maps:
  C-x  → mostly global map
  C-c  → mostly major mode map + user custom
  SPC  → Doom's unified interface (Evil minor mode map)
  M-x  → command search across everything

SPC works because Evil mode is a minor mode, and minor modes have the highest precedence. Doom routes everything through it, giving you one consistent interface. But the underlying system is still keymaps all the way down.

Resources

The keybinding system is one of those things where the complexity is the feature. Emacs has survived 40 years because new paradigms (Vim! Doom! which-key!) can layer on top without breaking what came before. Once you see the layers, it stops feeling random and starts feeling like archaeology — every stratum tells you something about when it was built and what problem it solved.