Keybindings
Remap Claude Code's keyboard shortcuts with ~/.claude/keybindings.json, with every context, action, default key and the keystroke syntax.
Every keyboard shortcut in Claude Code is an action bound to a keystroke inside a context. You can change any of them, apart from a handful of reserved keys, by editing ~/.claude/keybindings.json. Run /keybindings to create the file (or open it if it exists). Changes are picked up live; there is no need to restart.
If a key is not reaching Claude Code at all (Shift+Enter submitting, Option doing nothing on a Mac), that is a terminal problem rather than a binding problem. See terminal configuration first.
File format
The file is a JSON object with a bindings array. Each entry names a context and maps keystrokes to action names. Two optional keys help your editor: $schema enables autocompletion and validation, and $docs is just a reference URL.
Here is the file I actually use. It sends on Ctrl+Enter only (so a stray Enter adds a line), puts the external editor on Ctrl+E, frees Ctrl+S, and adds y/n to dialogs:
{
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"bindings": [
{
"context": "Chat",
"bindings": {
"enter": "chat:newline",
"ctrl+enter": "chat:submit",
"ctrl+e": "chat:externalEditor",
"ctrl+s": null
}
},
{
"context": "Confirmation",
"bindings": {
"y": "confirm:yes",
"n": "confirm:no"
}
}
]
}
Note:
ctrl+enteronly arrives as a distinct key in terminals that report extended keys. In others it looks like plain Enter, so test before you commit to a layout like this.
Setting an action to null removes the default binding for that key.
Contexts
A context says when a block of bindings is live.
| Context | Active when |
|---|---|
Global | Always |
Chat | The main prompt input has focus |
Autocomplete | A suggestion menu is open |
Settings | The /config settings panel is open |
Confirmation | A permission or confirmation dialog is showing |
Tabs | A tabbed component has focus |
Help | The help menu is open |
Transcript | The transcript viewer is open |
HistorySearch | You are searching history (Ctrl+R) |
Task | A task is running in the foreground |
ThemePicker | The theme picker is open |
Attachments | Navigating image attachments in a select dialog |
Footer | A footer item (tasks, teams, diff, artifacts) is selected |
MessageSelector | Choosing a message in the rewind dialog |
DiffDialog | The older diff viewer is open |
DiffPanel | The fullscreen diff panel is open |
ModelPicker | The model picker is open (effort controls) |
EffortSlider | The /effort slider is open |
Select | Any generic list or select component |
Plugin | The plugin browser and manager |
AbovePrompt | The band above the prompt, or a button in it, has focus |
AbovePromptInput | An input in that band, or in a mod pane, has focus |
AbovePromptSelect | A select in that band, or in a mod pane, has focus |
Pane | A pane drawn by a mod has focus |
PaneField | An input or select inside a mod pane has focus |
Agents | Agent view, opened with claude agents |
Scroll | Scrolling and selection in fullscreen rendering |
Versions before v2.1.205 also had a Doctor context with a doctor:fix action for /doctor.
Actions by context
Action names follow namespace:action. "Unbound" means there is no default key, so the action does nothing until you assign one.
Global
| Action | Default | Does |
|---|---|---|
app:interrupt | Ctrl+C | Cancels the current operation |
app:exit | Ctrl+D | Quits; press twice within 800 ms to confirm |
app:redraw | Unbound | Forces a terminal redraw |
app:toggleTodos | Ctrl+T | Shows or hides Claude's to-do checklist (not the /tasks background-task view) |
app:toggleTranscript | Ctrl+O | Toggles the verbose transcript |
History
| Action | Default | Does |
|---|---|---|
history:search | Ctrl+R | Opens history search |
history:previous | Up | Older prompt |
history:next | Down | Newer prompt |
Chat
| Action | Default | Does |
|---|---|---|
chat:submit | Enter | Sends the message |
chat:newline | Ctrl+J | Inserts a line break |
chat:cancel | Escape | Cancels the current input |
chat:clearInput | Ctrl+L | Full redraw; keeps your input and the conversation |
chat:clearScreen | Cmd+K | Same as chat:clearInput (iTerm2 and Terminal.app intercept Cmd+K themselves; see fullscreen) |
chat:cycleMode | Shift+Tab | Cycles permission modes |
chat:modelPicker | Meta+P | Opens the model picker |
chat:fastMode | Meta+O | Toggles fast mode |
chat:thinkingToggle | Meta+T | Toggles extended thinking |
chat:queueSubmit | Ctrl+X Enter | Sends but always queues behind the current turn, never interrupting; submits even when an autocomplete item is highlighted (v2.1.247+) |
chat:sendNow | Ctrl+Enter, Ctrl+X Ctrl+S | Sends queued messages plus your draft immediately; with nothing running it just submits, and in shell mode it only queues (v2.1.275+). The chord works in every terminal |
chat:undo | Ctrl+_, Ctrl+Shift+- | Undoes the last edit |
chat:externalEditor | Ctrl+G, Ctrl+X Ctrl+E | Opens the prompt in your editor; agent view's dispatch input uses the single-key bindings of this action |
chat:stash | Ctrl+S | Stashes the current prompt |
chat:imagePaste | Ctrl+V (Alt+V on Windows and WSL; WSL binds both) | Pastes an image from the clipboard |
chat:killAgents | Ctrl+X Ctrl+K | Stops all background subagents and disables artifact auto-replies for the session; press twice within 3 seconds. Works even while a background subagent's permission prompt is open |
On Windows without VT mode (Node older than 24.2.0 or 22.17.0, Bun older than 1.2.23), Shift+Tab defaults become Meta+M, for both chat:cycleMode and confirm:cycleMode.
Voice (Chat context)
| Action | Default | Does |
|---|---|---|
voice:pushToTalk | Space | Dictates, hold or tap depending on /voice mode. Only one key at a time; a custom key replaces Space. See voice dictation |
Autocomplete
| Action | Default | Does |
|---|---|---|
autocomplete:accept | Tab | Takes the suggestion |
autocomplete:dismiss | Escape | Closes the menu |
autocomplete:previous | Up | Previous suggestion |
autocomplete:next | Down | Next suggestion |
Confirmation and permissions
| Action | Default | Does |
|---|---|---|
confirm:yes | Enter | Accepts |
confirm:no | Escape | Declines or cancels |
confirm:previous | Up | Previous option |
confirm:next | Down | Next option |
confirm:nextField | Tab | In the /fast dialog, flips fast mode |
confirm:previousField | Unbound | Ignored; kept so old files stay valid |
confirm:toggle | Space | Toggles a selection |
confirm:cycleMode | Shift+Tab | On a file permission prompt, closes an open comment field, or else picks the "allow for this session" option when offered |
permission:toggleDebug | Unbound | Ignored; kept so old files stay valid |
Every dialog uses confirm:yes and confirm:no for accept and cancel, even if it is not a yes/no question. So if you bind bare y and n here, they act on all dialogs, including ones that never mention those letters. Dialogs that display y/n as options read the letters themselves anyway.
Up to v2.1.280, y and n were bound by default. If /keybindings created your file before then, those two lines are in it and remain active until you delete them. confirm:toggleExplanation (Ctrl+E, a model-written explanation on Bash and PowerShell prompts) existed before v2.1.257.
In most dialogs, a double Ctrl+C or Ctrl+D closes the dialog rather than quitting; the hint after the first press tells you which.
Transcript
| Action | Default | Does |
|---|---|---|
transcript:toggleShowAll | Ctrl+E | Shows all content (classic renderer only) |
transcript:exit | q, Ctrl+C, Escape | Leaves the transcript |
History search
| Action | Default | Does |
|---|---|---|
historySearch:next | Ctrl+R | Next match |
historySearch:accept | Escape, Tab | Keeps the match in the prompt |
historySearch:cancel | Ctrl+C | Abandons the search |
historySearch:execute | Enter | Runs the match |
historySearch:cycleScope | Ctrl+S | Cycles session, project, everywhere |
The first four apply to the classic renderer's inline search, which always covers every project. historySearch:cycleScope only matters in fullscreen, where Ctrl+R opens a dialog instead; that dialog's Enter/Tab (place match) and Esc (cancel) are fixed.
Task, theme, help
| Context | Action | Default | Does |
|---|---|---|---|
Task | task:background | Ctrl+B, Ctrl+X Ctrl+B | Sends the running task to the background |
ThemePicker | theme:toggleSyntaxHighlighting | Ctrl+T | Toggles syntax highlighting |
Help | help:dismiss | Escape | Closes help |
Tabs
| Action | Default | Does |
|---|---|---|
tabs:next | Tab, Right | Next tab |
tabs:previous | Shift+Tab, Left | Previous tab |
These work while the tab row has focus; in some dialogs (/help, /sandbox) they also work from inside the content. Up and Down move focus between the tab row and the content, and lists only respond while focused.
Attachments
| Action | Default | Does |
|---|---|---|
attachments:next | Right | Next image |
attachments:previous | Left | Previous image |
attachments:remove | Backspace, Delete | Removes the selected image |
attachments:exit | Down, Escape | Leaves attachment navigation |
Footer
| Action | Default | Does |
|---|---|---|
footer:next | Right | Next item |
footer:previous | Left | Previous item |
footer:up | Up, Ctrl+P | Up (deselects at the top) |
footer:down | Down, Ctrl+N | Down |
footer:openSelected | Enter | Opens the item |
footer:clearSelection | Escape | Deselects |
footer:close | x | Stops the selected agent or workflow, or dismisses its row if finished |
footer:dismiss | Unbound | No effect; kept for compatibility (it used Backspace/Delete before v2.1.281) |
With a footer item selected, Enter opens it even if you remapped Enter in Chat. Chat keys the footer does not use, such as Shift+Tab, keep working.
Select
| Action | Default | Does |
|---|---|---|
select:next | Down, J, Ctrl+N | Next option |
select:previous | Up, K, Ctrl+P | Previous option |
select:pageUp | PageUp | Page up |
select:pageDown | PageDown | Page down |
select:first | Home | First option |
select:last | End | Last option |
select:accept | Enter | Chooses |
select:cancel | Escape | Cancels |
List panels (/skills, /mcp, /tasks) honour your bindings for all four paging actions. Most other lists, /model included, honour select:first and select:last but always page with PageUp and PageDown. (Before v2.1.280 those lists ignored Home/End; before v2.1.283 /mcp's tool list used fixed paging keys.)
Message selector (rewind)
The rewind list uses Select actions and your Select bindings. MessageSelector has no actions of its own; use it to override a Select action for this list only:
{
"bindings": [
{ "context": "MessageSelector", "bindings": { "space": "select:accept" } }
]
}
Before v2.1.283 the list had messageSelector:up, :down, :top, :bottom and :select. Old bindings to those names still work, mapped to the matching Select action. Home and End jump to the ends (formerly Shift+K and Shift+J).
Model picker and effort slider
| Context | Action | Default | Does |
|---|---|---|---|
ModelPicker | modelPicker:decreaseEffort | Left | Lower effort |
ModelPicker | modelPicker:increaseEffort | Right | Higher effort |
ModelPicker | modelPicker:thisSessionOnly | s | Use the highlighted model for this session only |
EffortSlider | effortSlider:decreaseEffort | Left | Lower effort (v2.1.284+) |
EffortSlider | effortSlider:increaseEffort | Right | Higher effort (v2.1.284+) |
EffortSlider | effortSlider:toggleUltracode | Tab | Ultracode on or off for this session, where offered (v2.1.284+) |
EffortSlider | effortSlider:thisSessionOnly | s | Apply the focused effort to this session only (v2.1.257+) |
The slider's Enter and Escape cannot be rebound. See model configuration for effort levels.
Plugin
| Action | Default | Does |
|---|---|---|
plugin:toggle | Space | Selects or deselects a plugin |
plugin:install | I | Installs selected plugins |
plugin:favorite | F | Favourites a plugin so it sorts near the top of Installed |
Settings
The settings panel reuses two actions. Changes save immediately, so Escape closes without discarding anything.
| Action | Default | Does |
|---|---|---|
settings:search | / | Search settings |
settings:retry | R | Retry loading usage data after an error |
select:accept | Enter, Space | Change the setting or open its submenu |
confirm:no | Escape | Close the panel |
Agents (agent view)
Requires v2.1.257 or later.
| Action | Default | Does |
|---|---|---|
agents:switchView | Ctrl+S | Group sessions by state or by directory |
agents:togglePin | Ctrl+T | Pin or unpin the selected session |
agents:find | Ctrl+F | Find sessions by name using the n: filter (v2.1.288+) |
agents:rename | Ctrl+R | Rename the selected session (v2.1.288+) |
agents:previousGroup | Ctrl+Up, Meta+Up | Previous group header (v2.1.288+) |
agents:nextGroup | Ctrl+Down, Meta+Down | Next group header (v2.1.288+) |
In agent view an Agents binding wins over any Chat or Global binding on the same key, so Ctrl+S groups rather than stashing. Bindings there fire on single keystrokes, so chords (like Ctrl+X Ctrl+E) do not work; the dispatch input's external editor follows chat:externalEditor's single-key binding, Ctrl+G by default.
Scroll and selection (fullscreen)
| Action | Default | Does |
|---|---|---|
scroll:lineUp | wheelup | One line up |
scroll:lineDown | wheeldown | One line down |
scroll:pageUp | PageUp | Half a viewport up |
scroll:pageDown | PageDown | Half a viewport down |
scroll:halfPageUp | Unbound | Half a viewport up (alias for vi-style maps) |
scroll:halfPageDown | Unbound | Half a viewport down |
scroll:fullPageUp | Unbound | Full viewport up |
scroll:fullPageDown | Unbound | Full viewport down |
scroll:top | Ctrl+Home | Start of conversation |
scroll:bottom | Ctrl+End | Latest message; resumes auto-follow |
selection:copy | Ctrl+Shift+C / Cmd+C | Copies the selection |
selection:clear | Unbound | Clears the selection (v2.1.234+) |
selection:extendLeft | Shift+Left | Extend one column left |
selection:extendRight | Shift+Right | Extend one column right |
selection:extendUp | Shift+Up | Extend one row up, scrolling at the edge |
selection:extendDown | Shift+Down | Extend one row down, scrolling at the edge |
selection:extendLineStart | Shift+Home | Extend to line start |
selection:extendLineEnd | Shift+End | Extend to line end |
MacBook users: Ctrl+End cannot be typed, so give scroll:bottom something reachable. A vi-flavoured example:
{
"bindings": [
{
"context": "Scroll",
"bindings": {
"ctrl+u": "scroll:halfPageUp",
"ctrl+f": "scroll:halfPageDown",
"meta+g": "scroll:bottom"
}
}
]
}
Ctrl+D would be the natural vi partner for Ctrl+U, but it is reserved for exit, hence Ctrl+F here.
Diff viewer (older dialog)
These reach only the older diff viewer, which /diff opens outside fullscreen after you disable the built-in cc-plugin-diff mod in /plugin. With the mod enabled, /diff shows the newer dialog. Files naming these actions load fine either way.
| Action | Default | Does |
|---|---|---|
diff:dismiss | Escape | Closes, or returns from detail to the file list |
diff:previousSource | Left | Previous diff source |
diff:nextSource | Right | Next diff source |
diff:previousFile | Up, K | Previous file, or up a line in detail view |
diff:nextFile | Down, J | Next file, or down a line in detail view |
diff:back | Unbound | Back (Escape already does this; Left was removed in v2.1.203) |
The file list also honours Select actions (select:previous, select:next, and Enter via select:accept). Bind them in a DiffDialog block to change them for this list only. diff:viewDetails from before v2.1.283 still works as select:accept.
The detail view adds pager keys within DiffDialog: scroll:pageUp (PageUp), scroll:pageDown (PageDown), scroll:fullPageUp (Shift+Space, B), scroll:fullPageDown (Space), scroll:top (G, Home), scroll:bottom (Shift+G, End). The Scroll context defaults are unaffected.
Diff panel (fullscreen)
app:cycleDiffBase lives in DiffPanel; the rest are Global. The built-in cc-plugin-diff mod draws this panel and handles the first three below; the toggle actions only reach the older panel shown when that mod is disabled.
| Action | Default | Does |
|---|---|---|
app:cycleDiffBase | Ctrl+X B | Compare against this session, uncommitted, or branch |
app:diffFileListUp | Ctrl+Up, Meta+Up | Scroll an overflowing file list up |
app:diffFileListDown | Ctrl+Down, Meta+Down | Scroll it down |
app:toggleReplTab | Unbound | Open or close the panel |
app:toggleDiffNoiseFilter | Unbound | Hide or show test and generated files |
app:toggleDiffPreSession | Unbound | Expand or collapse pre-session changes |
Band above the prompt
The band is where mods draw buttons, inputs and selects. abovePrompt:toggle and abovePrompt:focus apply in Chat; the others apply in whichever context has focus.
| Action | Default | Does |
|---|---|---|
abovePrompt:toggle | Ctrl+X Ctrl+A | Collapse the band to a one-row hint or expand it |
abovePrompt:focus | Ctrl+X Tab | Move focus into the band, then each open pane, then back to the prompt |
abovePrompt:next | Tab | Next control |
abovePrompt:previous | Shift+Tab | Previous control |
abovePrompt:press | Enter | Press a button, submit an input, or pick a select option |
abovePrompt:leave | Escape | Back to the prompt |
abovePrompt:highlightNext | Down | Next option in a focused select |
abovePrompt:highlightPrevious | Up | Previous option in a focused select |
Extra defaults: in AbovePrompt, Right/Left also mean next/previous and Space means press; in AbovePromptInput, Down/Up mean next/previous. AbovePrompt also binds Up, Down, PageUp, PageDown, Home and End to the pane scroll actions below, so rebind those in an AbovePrompt block to change them for the band.
Panes
| Action | Default | Does |
|---|---|---|
pane:scrollUp | Up | Scroll up |
pane:scrollDown | Down | Scroll down |
pane:pageUp | PageUp | Page up |
pane:pageDown | PageDown | Page down |
pane:top | Home | Top |
pane:bottom | End | Bottom |
pane:grow | Ctrl+X Left, Ctrl+X Up | More width (beside the transcript) or height (above the prompt) |
pane:shrink | Ctrl+X Right, Ctrl+X Down | Less room |
pane:close | Ctrl+X X | Close the pane (also in PaneField) |
pane:next | Unbound | Next open pane (Global, when several are open) |
pane:previous | Unbound | Previous open pane |
Pane also maps Tab, Shift+Tab, Enter and Escape to the band actions, and pane fields use AbovePromptInput and AbovePromptSelect. See mod interface for what a mod receives.
Keystroke syntax
Modifiers
Join with +:
| Write | Means |
|---|---|
ctrl, control | Control |
shift | Shift |
alt, opt, option, meta | Alt on Windows/Linux, Option on macOS |
cmd, command, super, win | Cmd on macOS, Windows key, Super on Linux |
The cmd family is only seen in terminals that report Super (kitty keyboard protocol, or xterm modifyOtherKeys). Most do not, so prefer ctrl or meta for anything you want portable. Examples: ctrl+g, shift+tab, meta+o, ctrl+shift+x.
Case
Key names are case-insensitive: ctrl+G equals ctrl+g. For Shift plus a letter, write shift+g.
Non-US layouts
Always write Ctrl shortcuts with Latin letters.
- Non-Latin layouts (e.g. Cyrillic): if the terminal uses the kitty keyboard protocol and reports physical key positions, Ctrl shortcuts match by US-layout position, so Ctrl plus the physical W key fires
ctrl+w. Otherwise Claude Code matches what the terminal sends: an ASCII control code fires the Latin shortcut, but a Cyrillic character matches nothing. - Rearranged Latin layouts (e.g. AZERTY): the letter the key types is what matches, so Ctrl and the key labelled A fires
ctrl+a.
Chords
Separate keystrokes with spaces, for example ctrl+x ctrl+e. Each press must follow within 3 seconds or the chord is cancelled with a short notice.
Special key names
escape/esc, enter/return, tab, space, up, down, left, right, pageup, pagedown, home, end, backspace, delete, and the mouse wheel events wheelup and wheeldown.
Unbinding and chord prefixes
null removes a binding, chords included. A prefix such as ctrl+x stays reserved (pressing it waits for the second key) while any chord in any active context starts with it. To reclaim the prefix for a single key, unbind every chord that uses it, in the context that defines it.
Default ctrl+x chords:
| Context | Chords |
|---|---|
Chat | ctrl+x ctrl+k, ctrl+x ctrl+e, ctrl+x enter (v2.1.247+), ctrl+x ctrl+a (v2.1.260+), ctrl+x ctrl+s (v2.1.275+), ctrl+x tab (v2.1.260+) |
Task | ctrl+x ctrl+b |
DiffPanel | ctrl+x b (v2.1.260+) |
Pane | ctrl+x left, ctrl+x right, ctrl+x up, ctrl+x down, ctrl+x x |
PaneField | ctrl+x x |
For example, to make ctrl+x itself open the external editor:
{
"bindings": [
{ "context": "Task", "bindings": { "ctrl+x ctrl+b": null } },
{ "context": "DiffPanel", "bindings": { "ctrl+x b": null } },
{
"context": "Pane",
"bindings": {
"ctrl+x left": null, "ctrl+x right": null,
"ctrl+x up": null, "ctrl+x down": null, "ctrl+x x": null
}
},
{ "context": "PaneField", "bindings": { "ctrl+x x": null } },
{
"context": "Chat",
"bindings": {
"ctrl+x ctrl+k": null, "ctrl+x ctrl+e": null, "ctrl+x enter": null,
"ctrl+x ctrl+a": null, "ctrl+x ctrl+s": null, "ctrl+x tab": null,
"ctrl+x": "chat:externalEditor"
}
}
]
}
Leave even one chord in place and the prefix keeps waiting for a second key. Bear in mind you lose the default way to kill agents, send now, toggle the band and so on, so rebind whichever of those you use.
Reserved keys
| Key | Why it cannot be rebound |
|---|---|
| Ctrl+C | Fixed interrupt/cancel |
| Ctrl+D | Fixed exit |
| Ctrl+M | Always arrives as Enter |
| Ctrl+[ | Always arrives as Escape (kitty protocol terminals from v2.1.242) |
| Ctrl+I | Always arrives as Tab |
| Ctrl+H | The ASCII backspace byte; Windows handling depends on the terminal and CLAUDE_CODE_BS_AS_CTRL_BACKSPACE (see terminal configuration) |
| Caps Lock | Never delivered to terminal apps |
Clashes with multiplexers and the shell
| Key | Taken by |
|---|---|
| Ctrl+B | tmux prefix (press twice to pass it through) |
| Ctrl+A | GNU screen prefix |
| Ctrl+Z | Unix suspend (SIGTSTP) |
Bare keys and text fields
If you bind a plain letter, digit or Space, you can still type it in dialog text fields, such as the Other answer to a question. While such a field is focused, unmodified printable keys go to the field and skip binding lookup. Keys that still run bindings there:
- non-printing keys: Enter, Escape, Tab, arrows
- anything with Ctrl, Alt or Cmd
- the second half of a chord already in progress
At the main prompt it works the other way round: every key is checked against active contexts first, and typed only if nothing claims it.
Vim mode
With Vim editing on (/config → Editor mode), the two systems work at different layers:
- Vim handles text-level input: motions, modes, operators.
- Keybindings handle component-level actions: submit, toggle to-dos and so on.
- Escape switches INSERT to NORMAL and does not fire
chat:cancel. - Most Ctrl shortcuts pass through to keybindings.
- Vim keys cannot be remapped here; for an INSERT-mode pair such as
jkto Escape usevimInsertModeRemaps(see interactive mode). - In NORMAL mode,
?opens help and/opens history search.
Validation
Problems are written to the debug log as warnings. Start with claude --debug to see them. What gets flagged:
- invalid JSON or wrong structure
- misspelt modifiers like
crtl+k(the unknown part is dropped, so the binding lands on plaink, which is rarely what you want) - unknown context names
- action values that are neither a string nor
null - unknown action names (the binding is skipped and the default for that key stays)
- attempts to bind reserved keys
- the same key bound twice in one context