Claude Code Keybindings Keyboard Shortcuts 2026

Claude Code Keybindings: The Complete Guide to Custom Keyboard Shortcuts (2026)

The Prompt Shelf ·

Claude Code’s keyboard shortcuts are configurable through a single file: ~/.claude/keybindings.json. Run /keybindings to create or open it. Each entry maps a keystroke to an action written as namespace:actionchat:submit, app:toggleTodos, history:search — scoped to one of 19 contexts, and changes apply live without restarting the session.

That’s the mechanism in one paragraph. The part that trips people up is everything downstream of it: which of the 19 contexts a given shortcut actually lives in, why unbinding one chord doesn’t free up the key you expected, why vim mode ignores your keybindings file entirely for text editing, and which defaults quietly changed between versions because they collided with something else. Most write-ups reproduce the official shortcut list and stop. This one goes through the full context/action surface, the keystroke syntax rules that produce confusing edge cases, and configs for the friction points people actually hit — tmux’s Ctrl+B prefix, reclaiming Ctrl+X, and matching VS Code or vim conventions without breaking something else.


Keybindings vs. vim mode: they don’t share a config

This is the single most common confusion, so it’s worth settling first:

Keybindings (keybindings.json)Vim mode (/config → Editor mode)
What it controlsComponent-level actions — submit, toggle todos, cycle permission modeText-input-level editing — cursor motion, NORMAL/INSERT/VISUAL modes
Config file~/.claude/keybindings.jsonA setting, not a file — toggled via /config
Can you remap it?Yes, per contextOnly partially — vimInsertModeRemaps lets you map two-key INSERT-mode sequences (like jj → Escape) to leave INSERT mode; the vim motions themselves aren’t remappable here
Escape key behaviorTriggers chat:cancel in the Chat contextSwitches INSERT → NORMAL; does not trigger chat:cancel
? and / in vim NORMAL modeN/A? opens the help menu, / opens history search — vim conventions, not keybinding actions

They run side by side. Most Ctrl+key shortcuts still pass through vim mode to the keybinding system — it’s specifically the plain-letter motions (h/j/k/l, dd, etc.) and the Escape-to-NORMAL behavior that live outside keybindings.json entirely.


Set it up

/keybindings

This creates ~/.claude/keybindings.json if it doesn’t exist and opens it. The file is a single object with a bindings array:

{
  "$schema": "https://www.schemastore.org/claude-code-keybindings.json",
  "$docs": "https://code.claude.com/docs/en/keybindings",
  "bindings": [
    {
      "context": "Chat",
      "bindings": {
        "ctrl+e": "chat:externalEditor",
        "ctrl+u": null
      }
    }
  ]
}

$schema is optional but worth keeping — it gets you autocomplete and inline validation in any editor that reads JSON Schema (VS Code does this natively). $docs is just a comment field. The example above rebinds Ctrl+E to open your $EDITOR from the chat input, and unbinds the default Ctrl+U.

Changes save and apply immediately. No restart, no /reload.


The 19 contexts

Each block in bindings targets exactly one context — the UI state a shortcut is active in. A binding for Chat does nothing while a confirmation dialog is focused, and vice versa.

ContextActive when
GlobalAlways — app-level actions like interrupt and exit
ChatTyping in the main prompt input
AutocompleteThe /command or @file autocomplete menu is open
Settings/config settings panel is open
ConfirmationA permission or confirmation dialog is showing
TabsTab-based navigation components
HelpThe help menu (?) is visible
TranscriptFull transcript viewer (Ctrl+O) is open
HistorySearchHistory search mode (Ctrl+R) is active
TaskA background task is running
ThemePickerThe theme picker dialog is open
AttachmentsNavigating between attached images in a select dialog
FooterFooter indicators (tasks, teams, diff, artifacts) have focus
MessageSelectorThe rewind/summarize message-picker dialog is open
DiffDialogThe diff viewer is open
ModelPickerThe model/effort picker is open
SelectGeneric select/list components (used by several dialogs)
PluginThe plugin browse/discover/manage dialog is open
ScrollConversation scrolling and text selection, fullscreen rendering only

A Doctor context existed for the /doctor diagnostics screen before v2.1.205 and no longer appears in the current schema — if you’re on an older build and see references to a doctor:fix action, that’s why it’s gone from current docs.


Keystroke syntax

Modifiers

ctrl+k          Ctrl + K
shift+tab       Shift + Tab
meta+p          Option+P on macOS, Alt+P elsewhere
ctrl+shift+c    Stack multiple modifiers with +

Accepted modifier names: ctrl/control, shift, alt/opt/option/meta (Alt on Windows/Linux, Option on macOS), and cmd/command/super/win (Cmd on macOS, Windows key on Windows, Super on Linux). That last group only works in terminals reporting the Super modifier — Kitty keyboard protocol or xterm’s modifyOtherKeys mode. Most terminal emulators don’t send it. If a binding needs to work everywhere, use ctrl or meta, not cmd.

Uppercase letters imply Shift — except with a modifier

A bare K in a binding means Shift+K — this is what makes vim-style bindings (lowercase and uppercase meaning different things) work at all. But once you add a modifier, the uppercase stops mattering: ctrl+K and ctrl+k are identical. This asymmetry catches people who assume the rule is consistent everywhere in the file.

Chords

Space-separated keystrokes fire in sequence:

ctrl+k ctrl+s   Press Ctrl+K, release, then Ctrl+S

Special keys

escape/esc, enter/return, tab, space, up/down/left/right, backspace/delete.


Unbinding, and why it doesn’t always free the key you expect

Set an action to null to remove a default:

{
  "bindings": [
    { "context": "Chat", "bindings": { "ctrl+s": null } }
  ]
}

Chords make this less obvious than it looks. Claude Code ships three default Ctrl+X chords spread across two contexts: ctrl+x ctrl+k and ctrl+x ctrl+e in Chat, plus ctrl+x ctrl+b in Task. Unbind only one and Ctrl+X still enters “chord-wait mode” the moment you press it, because the prefix is still reserved by the others. To actually reclaim Ctrl+X as a single-key binding, every chord sharing that prefix has to go, across every context that defines one:

{
  "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"
      }
    }
  ]
}

Note the ordering doesn’t matter across context blocks, but all three nulls have to exist somewhere in the file for ctrl+x to stop waiting for a second key.

Reserved — can’t be rebound at all

ShortcutReason
Ctrl+CHardcoded interrupt/cancel
Ctrl+DHardcoded exit
Ctrl+MTerminals send the same code as Enter (CR), so they’re indistinguishable
Caps LockNever delivered to terminal apps at all

Terminal multiplexer conflicts

ShortcutConflicts with
Ctrl+Btmux prefix — press twice to send through to Claude Code
Ctrl+AGNU screen prefix
Ctrl+ZUnix SIGTSTP (process suspend)

If you live in tmux, the default task:background binding (Ctrl+B, plus a Ctrl+X Ctrl+B chord added in v2.1.169 specifically to route around this) already has a workaround built in — use the chord instead of fighting your tmux prefix key.


Ready-to-use configs

Reclaim Ctrl+S from the terminal’s flow-control freeze. Some terminals still intercept Ctrl+S for XOFF before Claude Code sees it. If your chat:stash binding (default Ctrl+S) seems to do nothing, move it:

{
  "bindings": [
    { "context": "Chat", "bindings": { "ctrl+s": null, "ctrl+j": "chat:stash" } }
  ]
}

(Swap chat:newline’s default off Ctrl+J first if you use that one — check what’s already bound before overwriting.)

VS Code-style save-and-continue muscle memory, mapping Cmd+K behavior explicitly instead of relying on the default:

{
  "bindings": [
    {
      "context": "Chat",
      "bindings": { "cmd+k": "chat:clearScreen", "ctrl+g": "chat:externalEditor" }
    }
  ]
}

Vim-style navigation in the message selector (rewind/summarize dialog) — already the default, but explicit if you’ve unbound it elsewhere and want it back:

{
  "bindings": [
    {
      "context": "MessageSelector",
      "bindings": {
        "j": "messageSelector:down",
        "k": "messageSelector:up",
        "ctrl+n": "messageSelector:down",
        "ctrl+p": "messageSelector:up"
      }
    }
  ]
}

Turn off the accidental-Ctrl+D-exits-Claude-Code risk as much as the reserved-shortcut rule allows — you can’t unbind Ctrl+D itself (it’s hardcoded), but you can slow down accidental history-search exits that people often conflate with it by confirming historySearch:cancel requires a deliberate key:

{
  "bindings": [
    { "context": "HistorySearch", "bindings": { "ctrl+c": "historySearch:cancel" } }
  ]
}

Action reference by context

The full surface, condensed. Defaults shown are what ships out of the box — anything you rebind overrides these.

Global & history actions
ActionDefaultDescription
app:interruptCtrl+CCancel current operation
app:exitCtrl+DExit — press twice within 800ms to confirm
app:redrawunboundForce terminal redraw
app:toggleTodosCtrl+TToggle Claude’s to-do checklist (not the /tasks background view)
app:toggleTranscriptCtrl+OToggle verbose transcript
history:searchCtrl+ROpen history search
history:previous / history:nextUp / DownNavigate command history
Chat actions
ActionDefaultDescription
chat:cancelEscapeCancel current input
chat:clearInputCtrl+LRedraw screen, keep input; press twice within 2s in fullscreen to run /clear
chat:clearScreenCmd+KSame double-tap /clear behavior in fullscreen
chat:killAgentsCtrl+X Ctrl+KStop all running background subagents in this session
chat:cycleModeShift+TabCycle permission modes
chat:modelPickerMeta+POpen model picker
chat:fastModeMeta+OToggle fast mode
chat:thinkingToggleMeta+TToggle extended thinking
chat:submitEnterSubmit message
chat:newlineCtrl+JInsert newline without submitting
chat:undoCtrl+_, Ctrl+Shift+-Undo last action
chat:externalEditorCtrl+G, Ctrl+X Ctrl+EOpen input in $EDITOR
chat:stashCtrl+SStash current prompt
chat:imagePasteCtrl+V (Alt+V on Windows/WSL)Paste image from clipboard

Windows without VT mode (Node < 24.2.0/22.17.0, Bun < 1.2.23) falls back to Meta+M for chat:cycleMode instead of Shift+Tab.

Autocomplete, Confirmation & Permission actions
ActionDefaultDescription
autocomplete:acceptTabAccept suggestion
autocomplete:dismissEscapeDismiss menu
autocomplete:previous / :nextUp / DownNavigate suggestions
confirm:yesY, EnterConfirm
confirm:noN, EscapeDecline
confirm:previous / :nextUp / DownNavigate options
confirm:nextFieldTabNext field
confirm:toggleSpaceToggle selection
confirm:cycleModeShift+TabCycle permission modes
confirm:toggleExplanationCtrl+EToggle model-generated explanation on Bash/PowerShell permission prompts
permission:toggleDebugunboundToggle permission debug info — the old Ctrl+D default was removed in v2.1.146 because it shadowed app:exit
Transcript, History Search & Task actions
ActionDefaultDescription
transcript:toggleShowAllCtrl+EToggle show-all (classic renderer only)
transcript:exitq, Ctrl+C, EscapeExit transcript view
historySearch:nextCtrl+RNext match (classic renderer)
historySearch:acceptEscape, TabAccept selection
historySearch:cancelCtrl+CCancel search
historySearch:executeEnterRun selected command
historySearch:cycleScopeCtrl+SCycle session/project/everywhere scope — fullscreen only
task:backgroundCtrl+B, Ctrl+X Ctrl+BBackground the current task; the chord (v2.1.169+) exists specifically to dodge the tmux prefix
Dialog navigation: Theme, Help, Tabs, Attachments, Footer, MessageSelector, DiffDialog, ModelPicker, Select, Plugin, Settings
ActionDefaultDescription
theme:toggleSyntaxHighlightingCtrl+TToggle syntax highlighting in theme picker
help:dismissEscapeClose help menu
tabs:next / :previousTab/Right, Shift+Tab/LeftMove between tabs
attachments:next / :previousRight / LeftNavigate attached images
attachments:removeBackspace, DeleteRemove selected attachment
attachments:exitDown, EscapeExit attachment navigation
footer:next / :previousRight / LeftMove between footer items
footer:openSelectedEnterOpen selected footer item
footer:dismissBackspace, DeleteDismiss the selected artifact link from the footer — requires v2.1.217+
messageSelector:up / :downUp/K/Ctrl+P, Down/J/Ctrl+NNavigate rewind/summarize message list
messageSelector:top / :bottomCtrl+Up/Shift+Up/Meta+Up/Shift+K, Ctrl+Down/Shift+Down/Meta+Down/Shift+JJump to top/bottom
diff:dismissEscapeClose diff viewer (or return to file list from detail view)
diff:previousFile / :nextFileUp/K, Down/JNavigate files in the diff list
diff:backunboundGo back — the old default of Left in detail view was removed in v2.1.203
modelPicker:decreaseEffort / :increaseEffortLeft / RightAdjust effort level
modelPicker:thisSessionOnlysApply the highlighted model to this session only
select:next / :previousDown/J/Ctrl+N, Up/K/Ctrl+PGeneric list navigation
select:accept / :cancelEnter, EscapeAccept/cancel
plugin:toggleSpaceToggle plugin selection
plugin:installIInstall selected plugins
plugin:favoriteFPin plugin near the top of the Installed tab
settings:search/Enter search mode
settings:retryRRetry usage-data load on error

In Settings, select:accept and confirm:no are reused with different semantics: changes apply the instant you change a setting, so Escape closes the panel with edits already saved rather than reverting them.

Scroll actions (fullscreen rendering only)
ActionDefaultDescription
scroll:lineUp / :lineDownunboundOne-line scroll — mouse wheel triggers these
scroll:pageUp / :pageDownPageUp / PageDownHalf-viewport scroll
scroll:top / :bottomCtrl+Home / Ctrl+EndJump to start/latest message (bottom re-enables auto-follow)
scroll:halfPageUp / :halfPageDownunboundSame as page scroll, provided for vi-style rebinds
scroll:fullPageUp / :fullPageDownunboundFull-viewport scroll
selection:copyCtrl+Shift+C / Cmd+CCopy selected text
selection:extendLeft/Right/Up/DownShift+arrowsExtend text selection
selection:extendLineStart / :extendLineEndShift+Home / Shift+EndExtend to line boundaries

The DiffDialog detail view separately binds pager-style scroll keys (PageUp/PageDown/Shift+Space+B/Space/G+Home/Shift+G+End) as part of its own context — those aren’t the same bindings as the Scroll context defaults above, even though the actions share names.


Validation and debugging

Claude Code validates the file on load and warns about:

  • Parse errors (invalid JSON or structure)
  • Invalid context names
  • Reserved-shortcut conflicts (trying to rebind Ctrl+C, Ctrl+D, etc.)
  • Terminal multiplexer conflicts
  • Duplicate bindings within the same context

Warnings write to the debug log, not the main UI — start with claude --debug to actually see them.


Troubleshooting

My binding doesn’t fire at all. Check the context first. A Chat-scoped binding is inert while a Confirmation dialog has focus — this is the most common cause, since the two look similar at a glance but are entirely separate contexts.

I unbound a chord and the prefix still waits for a second key. You didn’t unbind every chord sharing that prefix. See the Ctrl+X example above — a prefix stays reserved as long as any active context still defines a chord on it.

Ctrl+D still exits even though I remapped permission:toggleDebug. Ctrl+D for app:exit is hardcoded and reserved; it can’t be unbound regardless of what else you map to it in another context.

My binding works on Mac but not on Windows (or vice versa). cmd/command/super/win only register in terminals that report the Super modifier (Kitty protocol, xterm modifyOtherKeys). Most Windows terminals and plain xterm don’t send it — rebind with ctrl or meta if cross-platform behavior matters.

Vim mode is on and my keybinding for a plain letter doesn’t do what I mapped. Vim mode owns plain-letter input in Chat for motions and mode-switching — it isn’t routed through keybindings.json. Rebind vim’s own INSERT-mode-exit sequence via vimInsertModeRemaps instead, and keep keybindings.json changes to Ctrl+key combinations, which do still pass through.


FAQ

Do keybinding changes need a restart? No — the file is watched and reloaded automatically.

Can I have different keybindings per project? No. Unlike settings.json, keybindings.json doesn’t have a project-level variant documented — it’s a single user-level file at ~/.claude/keybindings.json.

What happens if I bind the same key twice in one context? Claude Code flags it as a duplicate-binding warning in the debug log (claude --debug); the behavior in that case is undefined enough that it’s worth just fixing the duplicate rather than relying on which one wins.

Why does ctrl+K behave the same as ctrl+k, but plain K means Shift+K? Because the uppercase-implies-Shift rule only applies to bare keys with no modifier. The moment you add ctrl, alt, or another modifier, casing becomes purely stylistic.

Is there a way to see what’s currently bound without opening the JSON? The in-app help menu (? outside vim mode, or ? in vim NORMAL mode) lists active shortcuts for the current context.


Browse how real projects configure settings.json, hooks, and permissions in our rules gallery.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules