Claude Code Keybindings: Rebind, Unbind and Chords

In ~/.claude/keybindings.json, which you can create or open by running the /keybindings command. It holds a bindings array of blocks, each naming a context and a map of keystrokes to actions. Changes are detected and applied automatically without restarting Claude Code.
Set the keystroke to null in the appropriate context block. This works for chord bindings too. But to reclaim a prefix key as a single-key binding, you must unbind every chord that uses it — a chord in any active context keeps its prefix reserved, and the default families can span more than one context.
Four. Ctrl+C is a hardcoded interrupt and Ctrl+D a hardcoded exit. Ctrl+M is reserved because terminals send an identical byte for it and for Enter, so binding it separately is not possible. Caps Lock is never delivered to terminal applications at all.
No, they operate at different layers. Vim mode handles input at the text level — cursor movement, modes, motions — while keybindings handle component-level actions. Escape in vim mode switches insert to normal mode rather than triggering cancel, and most control-key shortcuts pass through to the keybinding system.
Usually context or chord reservation. A chat action placed in the Global block is valid JSON and a valid action name, and completely inert. If the file validates and the binding still does nothing, check the context first, then whether the key is the prefix of a chord in any active context. Start with --debug to see validation detail.

Key Takeaway
Claude Code keybindings live in a single file created by the keybindings command, and changes apply without restarting. Bindings are scoped to a context such as Chat or Confirmation, actions use a namespace-colon-action form, and setting an action to null unbinds a default. Four shortcuts cannot be rebound at all.
I wanted a single key for inserting a newline and picked one that was free. It refused to bind, silently, and I assumed the file was wrong. It was not: the key I picked was the prefix of three default chords, and a chord in any active context keeps its prefix reserved. My binding was competing with a mechanism I had not noticed existed.
The keybinding system is more structured than most terminal tools bother with — nineteen contexts, a namespaced action list, real chord support — and the structure is what makes some rebinds refuse to work for reasons that are not obvious. This post covers the file, contexts, the chord prefix rule, the reserved keys and why each is reserved, and how vim mode and keybindings coexist without fighting.
Run the keybindings command and Claude Code creates or opens the file for you. It is an object with an array of blocks, each naming a context and a map of keystrokes to actions. The schema URL at the top is optional and worth keeping — it gives you autocompletion for action names in any editor that reads it, which matters because there are well over a hundred.
// ~/.claude/keybindings.json — run /keybindings to create it.
// Changes are detected and applied WITHOUT restarting.
{
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"bindings": [
{
"context": "Chat",
"bindings": {
"ctrl+e": "chat:externalEditor",
"ctrl+u": null
}
}
]
}
# Actions are namespace:action — chat:submit, app:toggleTodos.
# Setting one to null unbinds the default.
#
# Modifiers join with +: ctrl shift alt|opt|option|meta
# cmd|command|super|win
# The cmd group is only detected in terminals that report the
# Super modifier (Kitty protocol, xterm modifyOtherKeys). Most
# do not — use ctrl or meta for bindings that must work anywhere.Every binding belongs to a context, and a binding in the wrong one simply never runs. There are nineteen, and the split is finer than most people expect — a permission dialog, a diff viewer, a model picker and the plugin browser all have their own. The four you will actually touch are these.
The contexts worth knowing:
| Context | When it applies | Typical action |
|---|---|---|
| Global | Everywhere in the app | Interrupt, exit, toggle the transcript |
| Chat | The main input area | Submit, newline, external editor, cycle permission mode |
| Confirmation | Permission and confirmation dialogs | Yes, no, toggle the command explanation |
| Scroll | Conversation scrolling in fullscreen rendering | Page up and down, jump to bottom, extend a selection |
Most defaults are fine. These four are the ones I have seen people change and keep changed:
There is a readline flavour setting that changes a family of text-editing defaults at once rather than binding them individually: the word-delete key deletes to the previous whitespace, the word-forward keys stop at the end of a word rather than the start of the next one, and a delete-to-word-end binding appears. If you have muscle memory from a readline shell, that one setting is worth more than any individual rebind.
This is the mechanism that blocked my rebind, and it is worth understanding rather than working around. Chords are keystrokes separated by a space, and any chord in an active context reserves its first keystroke. Reclaiming that keystroke as a single-key binding means unbinding every chord that uses it — and the default family spans two contexts, so unbinding only the ones you can see leaves the prefix reserved.
# Chords are keystrokes separated by a space:
# "ctrl+k ctrl+s" press Ctrl+K, release, then Ctrl+S
#
# The rule that catches people: a chord in ANY active context
# keeps its prefix RESERVED. To reclaim ctrl+x as a single key
# you must unbind every chord that uses it — and the default
# ctrl+x family spans TWO contexts.
{
"bindings": [
{
"context": "Task",
"bindings": { "ctrl+x ctrl+b": null }
},
{
"context": "Chat",
"bindings": {
"ctrl+x ctrl+k": null,
"ctrl+x ctrl+e": null,
"ctrl+x": "chat:newline"
}
}
]
}
# Unbind SOME but not all chords on a prefix and pressing that
# prefix still enters chord-wait mode for the survivors.Each reservation has a concrete reason, and one of them is a fact about terminals rather than a decision by anyone. The interrupt and exit keys are hardcoded. The third is reserved because terminals send an identical byte for it and for Enter, so binding it separately is not physically possible. And the fourth is never delivered to terminal applications at all.
# Cannot be rebound, and the reasons are worth knowing:
Ctrl+C hardcoded interrupt / cancel
Ctrl+D hardcoded exit
Ctrl+M terminals send an identical byte to Enter (both CR)
Caps Lock never delivered to terminal applications at all
# Conflicts with things that are not Claude Code:
Ctrl+B tmux prefix — press twice to send it through
Ctrl+A GNU screen prefix
Ctrl+Z Unix process suspend
# This is why the background-task action gained the
# ctrl+x ctrl+b chord alongside its ctrl+b default: the chord
# sidesteps the tmux prefix entirely.Three more conflict with things outside Claude Code and cannot be fixed from this file. The tmux and GNU screen prefixes are consumed by the multiplexer before Claude Code sees them — in tmux you can press the key twice to send it through — and the suspend key is handled by the shell. If a binding works outside tmux and not inside it, the file is not the problem.
They operate at different layers, and knowing which layer owns what saves a lot of confusion:
In vim normal mode two keys behave as a vim user expects rather than as Claude Code otherwise would: the question mark shows the help menu, and the forward slash opens history search — the same as the standard-mode history search key. Those are deliberate concessions to vim muscle memory rather than accidents, and they are not documented anywhere you would look while editing a keybindings file.
Claude Code validates the file on load and warns about parse errors, invalid context names, invalid action values, reserved-shortcut conflicts and duplicate bindings within one context. Those warnings appear when the file loads and are also written to the debug log, so starting with the debug flag is how you see the detail rather than the summary.
If the file validates and the binding still does nothing, the usual causes are context and chord reservation, in that order. Check that the action you bound actually exists in the context you put it in — a chat action in the Global block is valid JSON, a valid action name, and completely inert.
Two things make this system easy once you know them: bindings are scoped, so half of all failures are a block in the wrong context, and chords reserve their prefix, so the other half is a key that is not as free as it looks. Everything else is a lookup. Keep the schema line at the top of the file for autocompletion, start with the debug flag the first time a binding misbehaves, and reach for the readline flavour setting before you rebind text editing key by key.
Sources & further reading