Keybindings System Overview
April 25, 2026 · View on GitHub
You can map key combinations to commands that perform actions in the editor. These bindings are defined per input mode (like vim.normal or vscode) and set up mostly in various keybindings.json files.
Key Concepts
- Input modes are named contexts that group keybindings.
- A stack of active input modes determines which keybindings are active
- Higher modes in the stack take priority over lower ones when resolving keybindings.
- Input modes are dynamically activated based on e.g. editor focus.
Input Mode Stack Mechanics
At any given time, a combination of modes is active. Here's how mode resolution works:
- Base Modes are always active, regardless of focus.
- Contextual Modes (like
vim.insertorvim.completion) are added depending on the state of the focused view. - Keybinding resolution walks the stack top to bottom, using the first matching binding.
Example input mode stack when using Vim keybindings:
vim.completion ← top (from `text.completion-mode`) -|
vim.insert ← added dynamically | These come from the active editor
vim ← from `text.modes` -|
vim.base ← bottom (from `editor.base-modes`) - This one is always active
Global vs active commands/contexts
Commands and contexts fall into two categories, global and active.
globalcontexts areeditor,vim.base,vscode.baseand any other context in theeditor.base-modessetting.activecontexts arevscode,vim,vim.normal,vim.insert,vim.completionetc.globalcommands can be bound inglobalcontexts andactivecontextsactivecommands can be bound inactivecontexts directly or inglobalcontexts by prefix the command with.
Examples:
// keybindings.json
{
"vim.base": { // global context
"G": "close-active-view", // global command bound in global context
"L": ".vim.delete-left", // active command bound in global context
},
"vim.normal": { // active context
"G": "close-active-view", // global command bound in active context
"L": "vim.delete-left", // active command bound in active context
},
}
Input mode settings
| Setting | Purpose |
|---|---|
editor.base-modes | Always-active modes, regardless of focus |
text.modes | Modes active when a text editor is focused. These are changed dynamically when using Vim keybindings |
text.default-mode | Default mode added to text.modes for a text editor |
text.completion-mode | Mode added when the completion window is visible |
editor.command-line-mode-low | Input mode added during command-line mode (low priority) |
editor.command-line-mode-high | Input mode added during command-line mode (high priority) |
editor.command-line-result-mode-low | Mode active during command-line result (low) |
editor.command-line-result-mode-high | Mode active during command-line result (high) |
terminal.base-mode | Always-active mode when terminal is focused |
terminal.default-mode | Optional additional terminal mode |
selector.base-mode | Always-active mode when a selector popup is open (defaults to popup.selector) |
Selector Popup Mode Composition
When a selector popup is open, the following rules apply:
-
The base mode (e.g.,
popup.selector) is always added. -
A scope-specific mode is added:
- For selector scope
themes→popup.selector.themes
- For selector scope
-
If the preview is focused, the preview mode is added:
- →
popup.selector.preview
- →
Input Mode Stack Examples
These examples assume using Vim keybindings, with the modes defined like this
| Context | Active Input Modes (Bottom → Top) |
|---|---|
| Selector popup for themes (preview not focused) | vim.base, popup.selector, popup.selector.themes |
| Selector popup for themes (preview focused) | vim.base, popup.selector, popup.selector.preview |
| Text editor in normal mode | vim.base, vim, vim.normal |
| Text editor in insert mode | vim.base, vim, vim.insert |
| Text editor in insert mode and completions open | vim.base, vim, vim.insert, vim.completion |
| Terminal in normal mode | vim.base, terminal, terminal.normal |
| Command-line in insert mode + completions | vim.base, vim, vim.insert, vim.command-line-low, vim.completion, vim.command-line-high |
Switching Keybinding Sets
Use the extra-settings key to change which keybinding scheme to use (Vim is the default):
Example
// app://config/settings.json
{
"extra-settings": ["app://config/settings-vim.json"]
}
This loads modes like vim.base, vim, vim.normal, etc.
To switch to a different scheme (e.g., VSCode-style), replace it with:
"extra-settings": ["app://config/settings-vscode.json"]
Creating Custom Keybindings
You can define your own keybindings and create new input modes. Check out the default keybindings for a lot more examples
Where to Place Keybindings
Create or edit the following file to add your own keybindings:
- Linux:
~/.nev/keybindings.json - Windows:
%HOME%/.nev/keybindings.json
Each top-level key represents an input mode, and the object under it defines key-to-command mappings.
Example: Add a Maximize View Shortcut
To bind <LEADER>m in Vim Normal mode and <C-m> in VSCode mode to maximize the current view:
// ~/.nev/keybindings.json
{
"vscode": {
"<C-m>": ["toggle-maximize-view"]
},
"vim.normal": {
"<LEADER>m": ["toggle-maximize-view"]
}
}
More examples
{
"vim.base": {
"<C-w>h": ["focus-view-left"]
},
"vim": {
":": ["command-line"]
},
"-vim.normal": ["D", "<C-r>"], // Clear keybindings defined in vim.normal mode in previous keybindings.json files
"vim.normal": {
"a": ["vim.insert-mode", "right"],
"i": ["vim.insert-mode"]
},
"vim.insert": {
"<C-w>": ["vim.delete-word-back"],
"<C-u>": ["vim.delete-line-back"]
},
"popup.selector": {
// selector global keybindings
},
"popup.selector.themes": {
// keybindings specific to theme selector
},
"vscode.base": {
// VSCode-style base bindings
},
"vscode": {
// VSCode-style editor bindings
}
}
Creating a Custom Input Mode
This example shows how to create a custom vim mode ( vim.my-mode).
Step 1: Configure Mode Behavior
Add the following to ~/.nev/settings.json to define how your custom mode should behave:
{
// Whether to allow character input to be inserted when not bound to commands
"input.vim.my-mode.handle-inputs": true, // default: false
// Whether to allow keybindings to trigger commands in this mode
"input.vim.my-mode.handle-actions": true, // default: true
// If true, prevents any commands from lower-priority modes from being executed
"input.vim.my-mode.consume-all-actions": false, // default: false
// If true, prevents any text input from being handled by lower modes
"input.vim.my-mode.consume-all-input": false, // default: false
// Controls how the cursor moves for certain commands
// "last" moves only the selection end, "first" moves the start, "both" moves both
"editor.text.cursor.movement.vim.my-mode": "last",
// Makes the cursor appear as a block (true) or a line (false)
"editor.text.cursor.wide.vim.my-mode": true
}
Step 2: Add Keybindings for the Custom Mode
Extend your keybindings.json to define the behavior of your new mode:
// ~/.nev/keybindings.json
{
"vim.my-mode": {
"<ESCAPE>": ["set-mode", "vim.normal"], // Exit to normal mode
"x": ["undo"] // Example: bind 'x' to undo
},
"vim.normal": {
"<C-i>": ["set-mode", "vim.my-mode"] // Enter your custom mode
}
}
Mode Switching for Text Editors: set-mode and remove-mode
set-mode
The command set-mode adds a new mode to the active text editor and removes other modes with the same prefix.
For example:
set-mode vim.normal→ removesvim.insert, addsvim.normalset-mode other.mode→ does not affectvim.*modes
remove-mode
If you want to manually remove a mode without replacing it:
["remove-mode", "other.mode"]
Special Keys
Use angle brackets for special keys:
<ENTER>,<ESCAPE>,<SPACE>,<BACKSPACE>,<TAB>,<DELETE>- Arrow keys:
<LEFT>,<RIGHT>,<UP>,<DOWN> - Others:
<HOME>,<END>,<PAGE_UP>,<PAGE_DOWN> - Function keys:
<F1>to<F12>
Modifiers
Modifiers use the following abbreviations:
C= ControlS= ShiftA= Alt
Example:
"<C-HOME>": ["command"] // CTRL+HOME
"<CS-a>": ["command"] // CTRL+SHIFT+a
Note: Uppercase letters (e.g. "A") are treated as SHIFT+a.
Multi-Key Sequences
Keybindings can be multi-key sequences, like "d<text_object>" or "<SPACE><C-g>". The editor uses a state machine for each mode that processes each key in sequence.
⚠️ Don't bind keys that are prefixes of other bindings in the same mode:
"a": ["command-a"],
"aa": ["command-aa"] // This will never be triggered because "a" triggers first
Clearing keybindings
You can clear keybindings defined in other keybindings.json files loaded before the current keybindings.json file by prefixing the context with -:
{
"-vim.normal": ["D", "<C-r>"], // Clear specific keybindings defined in vim.normal mode in previous keybindings.json files
}
Insert Mode Input Delay
In insert mode or other input-consuming modes, bindings like "jj" can coexist with normal typing:
"vim.insert": {
"jj": ["set-mode", "vim.normal"]
}
Behavior depends on timing:
j→ delay passed → insertsjjj→ quick press → exits to normal modejk→jinserted,khandled normally
Configure the delay with:
"editor.insert-input-delay": 300 // milliseconds
Repeatable Keybindings (* Modifier)
Use * to allow repeating the last part of a keybinding:
"vim.normal": {
"<C-w><*-f>-": ["change-font-size", -1],
"<C-w><*-f>+": ["change-font-size", 1]
}
After pressing <C-w>f, you can press + or - repeatedly without restarting the sequence.
Submodes and Composability
Submodes are used to compose complex keybindings using reusable parts (like Vim-style motions and text objects).
Defining Submodes
{
"#count": {
"<-1-9><o-0-9>": [""]
},
"vim#text_object": {
"<?-count>iw": ["(count* <#text_object.count>) (vim.word-inner) (inclusive)"],
"i{": ["(surround \"{\" \"}\" true)"],
}
}
#in the mode name indicates a sub mode- sub modes can be used in main modes with suffixes, e.g the
text_objectsubmode can be used in any mode starting withvim, thecountsubmode can be used in any mode. <?-count>= optional count prefix<#text_object.count>= pass captured count as numeric argument
Using Submodes in a Main Mode
"vim.normal": {
"<?-count>d<text_object>": ["vim.delete-move <text_object> <#count>"],
"<?-count>c<text_object>": ["vim.change-move <text_object> <#count>"]
}
This results in bindings like:
3d2iw→vim.delete-move "(count* 2) (vim.word-inner) (inclusive)" 3
How It Works Internally
- Each input mode is compiled into a state machine
- Each keybinding is a sequence of states (one per key), the end state contains the bound command
- Each key press advances the current state of each input modes state machine, going from top to bottom through the stack
- Reaching an end state executes the command and aborts all other input mode state machines in the input mode stack
- Submodes are inserted into the state machine of the main mode
- Repeatable sequences (
<*>) change the default mode after a keybinding finishes to the tagged state instead of the start state.