Skip to content
Keyboard Shortcuts

Keyboard Shortcuts

LeapMux ships with a VS Code-style keyboard shortcut system: every shortcut is a command bound to a key, and each binding is active only inside a context (for example, only when a dialog is open or a terminal is focused). This chapter explains how the system works, lists every default binding, and documents the override format the engine uses for customization.

How shortcuts work

A keyboard shortcut in LeapMux is the combination of three independent pieces:

  • Command — a named action with a human title, such as app.newAgent (“New Agent”) or app.closeActiveTab (“Close Active Tab”). The command holds the logic; it does not know which key triggers it.
  • Keybinding — a key string mapped to a command, optionally gated by a when-clause. For example, $mod+n runs app.newAgent when !dialogOpen (no dialog is open).
  • Context — a set of named boolean/string values evaluated the moment you press a key (for example dialogOpen, terminalFocused, or platform == "mac"). A binding fires only if its when-clause evaluates to true against the current context.

Because these are decoupled, the same command can be bound to several keys, and the same key can run different commands depending on what is focused.

The $mod key

Key strings use the tinykeys vocabulary. The most important token is $mod:

  • On macOS, $mod means Cmd (⌘).
  • On Windows and Linux, $mod means Ctrl.

So a single binding such as $mod+n is shown as ⌘N on a Mac and Ctrl+N everywhere else. The tables below give both forms.

Other modifiers are Shift, Alt (also written Option), Control/Ctrl, and Meta. Named keys used in the defaults include Escape, F5, F12, the arrow keys, PageUp/PageDown, the bracket and backslash keys, and the digits 09.

Chords

The engine supports chords — space-separated key sequences such as $mod+k $mod+s (press the first combination, then the second). Chords render correctly in tooltips and menus, but no default binding uses one. The override format supports chords (see Customizing keybindings), with one caveat on the desktop app noted later.

When shortcuts are suppressed

LeapMux binds shortcuts at the window level so they work no matter where focus sits, but it suppresses some of them to avoid hijacking your typing:

  • Input focus. When the cursor is in a text input, textarea, select, or any contenteditable element, a shortcut that has no modifier and is not a function key is suppressed — unless its when-clause explicitly references inputFocused. Shortcuts that use a modifier ($mod, Ctrl, Alt, Meta, Shift) or a plain function key (F1F12) always fire, regardless of focus.
  • IME composition. While you are composing text with an Input Method Editor (for example, typing Chinese, Japanese, or Korean), shortcuts are held back so they cannot interfere with your text entry.

Contexts (the when clause)

A binding’s when-clause is a small boolean expression evaluated against the active context. The available context values are:

ContextTypeMeaning
isDesktopbooleanRunning inside the Tauri desktop app.
platformstring"mac", "windows", or "linux".
dialogOpenbooleanA modal dialog is currently open.
activeTabTypestring"agent", "terminal", "file", or empty when no tab is active.
inputFocusedbooleanFocus is in an input, textarea, select, or contenteditable element.
editorFocusedbooleanFocus is inside a rich-text editor surface.
chatInputFocusedbooleanFocus is inside a chat message input.
terminalFocusedbooleanFocus is inside a terminal.

The when expression grammar supports || (or), && (and), ! (not), parentheses, ==/!= comparisons, identifiers, quoted strings, and the literals true/false. An empty or absent when means “always active.” Examples drawn from the defaults: !dialogOpen, dialogOpen, isDesktop, !dialogOpen && isDesktop, chatInputFocused, and terminalFocused && platform == "mac".

Default keyboard shortcuts

The tables below list every default binding. The macOS column uses Apple’s symbol glyphs in HIG order (⌃⌥⇧⌘, run together with no separators); the Windows / Linux column uses Ctrl/Alt/Shift joined with +. Where a command has more than one binding, both are shown separated by / — exactly as LeapMux renders them in tooltips and menus.

App

CommandmacOSWindows / LinuxActive when
Open Preferences⌘,Ctrl+,always

Open Preferences opens the Preferences dialog. It is a workspace binding (it works everywhere inside a workspace), not desktop-only. On the macOS desktop app it is also mirrored onto the native menu bar — see Desktop (Tauri) accelerator differences. For the dialog’s contents, see Settings & Preferences.

Tabs

CommandmacOSWindows / LinuxActive when
New Agent⌘NCtrl+Nno dialog open
New Terminal⌘TCtrl+Tno dialog open
Close Active Tab⌘WCtrl+Wno dialog open
New Agent Dialog⇧⌘NCtrl+Shift+Nno dialog open
New Terminal Dialog⇧⌘TCtrl+Shift+Tno dialog open
Toggle Floating Tab⇧⌘OCtrl+Shift+Ono dialog open
Switch to Tab 1–9⌘1⌘9 / ⌥1⌥9Ctrl+1Ctrl+9 / Alt+1Alt+9always
Previous Tab⌘[ / ⌘PageUpCtrl+[ / Ctrl+PageUpalways
Next Tab⌘] / ⌘PageDownCtrl+] / Ctrl+PageDownalways

Tab switching and navigation operate on the visible tabs of the focused tile (or on all tabs when no tile is focused). Previous/Next wrap around and do nothing if a tile has fewer than two tabs. See Tabs & Layout.

Layout

CommandmacOSWindows / LinuxActive when
Split Tile Horizontally⌘\Ctrl+\always
Split Tile Vertically⇧⌘\Ctrl+Shift+\always
Toggle Left Sidebar⇧⌘[Ctrl+Shift+[always
Toggle Right Sidebar⇧⌘]Ctrl+Shift+]always

Splitting acts on the focused tile. For more on tiling and sidebars, see Tabs & Layout.

View

CommandmacOSWindows / LinuxActive when
Scroll Active Tab Up One Page⌥PageUpAlt+PageUpno dialog open
Scroll Active Tab Down One Page⌥PageDownAlt+PageDownno dialog open

Page scrolling only acts when the active tab is an agent or a terminal.

Files

CommandmacOSWindows / LinuxActive when
Refresh Directory Tree⌘R / F5Ctrl+R / F5always
Toggle Hidden Files⇧⌘HCtrl+Shift+Halways

These act on the file browser. See File Browser.

Chat

CommandmacOSWindows / LinuxActive when
Send Message⌘JCtrl+Jchat input focused

Note: Send Message (⌘J / Ctrl+J) is a global way to submit the focused chat input, independent of the chat editor’s own Enter behavior. The Enter-to-send vs. Cmd/Ctrl+Enter-to-send choice is a separate, editor-level setting described in Chat editor keys below.

Terminal (macOS only)

These give Mac users emacs-style line and word motion under Cmd/Option + arrow keys. They are active only when a terminal is focused and the platform is macOS.

CommandmacOSActive when
Go to Line Start⌘←terminal focused, macOS
Go to Line End⌘→terminal focused, macOS
Go to Previous Word⌥←terminal focused, macOS
Go to Next Word⌥→terminal focused, macOS

Under the hood these send terminal control sequences (Ctrl-A, Ctrl-E, Esc-b, Esc-f) to the focused terminal. See Terminals.

Dialogs

CommandmacOSWindows / LinuxActive when
Close DialogEscEsca dialog is open

Esc closes the most recently opened dialog — unless that dialog is busy (for example, mid-operation), in which case it resists closing until the operation finishes.

New Workspace

CommandmacOSWindows / LinuxActive when
New Workspace Dialog⌥⌘NCtrl+Alt+Nno dialog open

See Workspaces.

Desktop app only

These are gated to the desktop app via isDesktop and have no effect in the browser.

CommandmacOSWindows / LinuxActive when
Open in External Editor⇧⌘ECtrl+Shift+Eno dialog open, desktop
Open Web Inspector⌥⌘I / F12Ctrl+Alt+I / F12desktop
Zoom In⌘= / ⌘Num+Ctrl+= / Ctrl+Num+desktop
Zoom Out⌘- / ⌘Num-Ctrl+- / Ctrl+Num-desktop
Actual Size⌘0 / ⌘Num0Ctrl+0 / Ctrl+Num0desktop
Quit Application⌘QCtrl+Qdesktop

Note: “Open in External Editor” works only in solo mode. In a distributed Hub/Worker setup, the active tab’s working directory lives on the Worker machine, not your local filesystem, so there is no local editor to launch and the command does nothing. See Running LeapMux for run modes, and Open in external editor (desktop, solo mode) below for the full feature.

The zoom, web-inspector, and quit shortcuts (“core” bindings) are mounted at the application root, so they work on every screen — including the launcher and the sign-in/setup pages — not just inside a workspace.

Open in external editor (desktop, solo mode)

The ⇧⌘E / Ctrl+Shift+E shortcut launches an external code editor against the active tab’s working directory. It is one face of a feature that also lives in the desktop workspace title bar as a split button.

Where it appears. The split button shows in the title bar only when all three conditions hold: you are on the desktop app in solo mode, the active tab has a working directory, and at least one editor was auto-detected on your machine. It is hidden in the browser, in distributed mode, and when no editor is found.

The two faces of the split button.

  • The main face reads “Open in {EditorName}” (with the editor’s icon) when you have a preferred editor, or “Open in …” with a generic icon when you have not yet picked one. With a preferred editor set, clicking the main face — or pressing ⇧⌘E / Ctrl+Shift+E — launches it against the active tab’s working directory. When no editor is preferred yet, the two faces diverge: clicking the main face opens the dropdown so you can pick one, while pressing the shortcut launches the first detected editor (and records it as your preferred editor).
  • The chevron opens a dropdown that lists every detected editor alphabetically (with a checkmark on the current preferred one), followed by a separator and “Refresh editor list”.

Note: Picking an editor from the dropdown only sets it as your preferred editor — it does not launch it. To actually open the editor, use the main face or the keyboard shortcut after selecting. “Refresh editor list” re-probes your machine for installed editors, useful after you install or remove one.

What gets opened. The editor is launched against the active tab’s working directory and only against an absolute, existing directory — relative paths, missing paths, and plain files are rejected, so nothing launches if the active tab has no real directory.

Detected editors. LeapMux auto-detects popular editors including VS Code, the JetBrains IDEs, Cursor, Zed, Sublime Text, and others, probing your PATH, known install locations, and (on macOS) .app bundles. The chevron dropdown shows exactly which editors were found on your machine.

Reading the macOS glyphs

GlyphModifier
Cmd ($mod on macOS)
Control
Option / Alt
Shift

A few key names also render specially: Escape shows as Esc, arrows as ← → ↑ ↓, NumpadAdd as Num+, NumpadSubtract as Num-, and Numpad0 as Num0. On macOS, modifiers always appear in the order ⌃⌥⇧⌘ with no separators, so Cmd renders last (for example, ⇧⌘N — the Shift glyph, then the Cmd glyph, then the key); on Windows and Linux they are joined with + (for example, Ctrl+Shift+N).

Tip: You do not have to memorize these. LeapMux appends the active shortcut to tooltips and dropdown menu items automatically — for example, “New Agent (⌘N)” — formatted for your platform.

Customizing keybindings

Warning: There is currently no supported way to customize keybindings. The engine reads per-account overrides from a custom_keybindings_json field, but nothing in the product writes that field: the Preferences dialog has no keybinding panel (its sections cover appearance, fonts, and debug logging — see Settings & Preferences), and there is no remote-control or admin command for it either. The override format below documents the mechanism for completeness, not a workflow you can perform today.

LeapMux’s shortcut engine is built to rebind, add, or remove shortcuts through account-level overrides stored as JSON. The rest of this section describes that format and how the engine merges it with the defaults.

Where overrides live

When present, overrides are read from your user-preferences record in the Hub database (field custom_keybindings_json), not from browser storage. Because the field is account-level, overrides would follow you across browsers and devices when you sign in to the same account. The value is a JSON-encoded array of override objects:

[
  { "command": "app.newAgent", "key": "$mod+Shift+a" },
  { "command": "app.closeActiveTab", "key": "" },
  { "command": "app.toggleHiddenFiles", "key": "$mod+k $mod+h" }
]

Each override has the shape:

FieldRequiredMeaning
commandyesThe command id to bind (for example app.newAgent).
keyyesA tinykeys key string, or "" (empty) to unbind.
whennoA when-clause; inherits the default’s clause if omitted.

Warning: Overrides are applied to workspace keybindings. The core desktop bindings (quit, zoom, web inspector) are not part of the override-merged set.

How overrides merge with the defaults

For each overridden command, LeapMux merges the overrides with the defaults using these rules:

  • The default binding(s) for that command are replaced by all non-empty-key overrides for it.
  • Each override inherits the default’s when-clause unless it specifies its own when.
  • A command may have multiple overrides — it gets bound to every non-empty key listed.
  • An override with "key": "" (empty) unbinds the command. If every override for a command has an empty key, the command is fully unbound.
  • An override for a command not in the defaults is appended as a brand-new binding (with no inherited when-clause).

Changes take effect immediately: when the overrides change, LeapMux re-merges and re-binds the workspace shortcuts without a reload.

Each override targets a command by its id. Each id corresponds to one of the actions in the default tables above; the bindable workspace command ids are:

Command idAction
app.newAgentNew Agent
app.newTerminalNew Terminal
app.closeActiveTabClose Active Tab
app.newAgentDialogNew Agent Dialog
app.newTerminalDialogNew Terminal Dialog
app.newWorkspaceDialogNew Workspace Dialog
app.toggleFloatingTabToggle Floating Tab
app.refreshDirectoryTreeRefresh Directory Tree
app.toggleHiddenFilesToggle Hidden Files
app.switchToTab1app.switchToTab9Switch to Tab 1–9
app.previousTab / app.nextTabPrevious / Next Tab
app.scrollActiveTabPageUp / app.scrollActiveTabPageDownScroll Active Tab Up / Down One Page
app.splitTileHorizontal / app.splitTileVerticalSplit Tile Horizontally / Vertically
app.toggleLeftSidebar / app.toggleRightSidebarToggle Left / Right Sidebar
app.openPreferencesOpen Preferences
app.openInExternalEditorOpen in External Editor
dialog.closeClose Dialog
chat.sendMessageSend Message
terminal.lineStart / terminal.lineEndGo to Line Start / End
terminal.wordLeft / terminal.wordRightGo to Previous / Next Word

Note: If the custom_keybindings_json field ever holds an invalid JSON value, the engine treats it as “no overrides” and silently ignores it, falling back to the defaults — so a malformed value never breaks the default shortcuts.

Desktop (Tauri) accelerator differences

On the desktop app, a small number of shortcuts are mirrored onto the native menu bar as accelerators, but only on macOS. Windows and Linux render their own controls through the app’s custom title bar instead.

Two commands get their accelerators synced onto the macOS menu:

  • Open Preferences (⌘,) appears as Preferences… in the LeapMux Desktop application menu (the leftmost app menu on macOS).
  • Open Web Inspector appears as Open Web Inspector in the Help submenu.

If either command’s binding is overridden, the macOS menu accelerator updates to match.

Warning: Chords cannot become macOS menu accelerators. A binding that uses a space-separated chord (such as $mod+k $mod+s) still works as a regular shortcut, but it will not appear as an accelerator on the native menu item.

The macOS menu also carries the usual system items in each submenu (About, Services, Hide, Quit, the standard Edit and Window commands, and so on). These come from macOS, not from the LeapMux shortcut registry, so their accelerators are the OS defaults you already know and are not affected by custom_keybindings_json.

Chat editor keys (not part of the global system)

The chat message editor has its own in-editor key handling that is separate from the global shortcut registry described above. The most important of these is how Enter behaves, governed by the Enter key mode preference (a per-browser setting; the default is Cmd/Ctrl+Enter sends):

  • Cmd/Ctrl+Enter sends (default): ⌘Enter / Ctrl+Enter sends the message; a plain Enter inserts a newline.
  • Enter sends: a plain Enter sends the message; Shift+Enter inserts a newline.

You can flip between the two with the toggle button in the chat editor toolbar. Other editor keys handle Markdown structure inside the message box — for example, Tab/Shift+Tab adjust list and heading levels, Backspace lifts blockquotes and code blocks, and Cmd/Ctrl+E toggles inline code. These editor keys are not bindable through custom_keybindings_json.

For more on writing and sending messages to agents, see Coding Agents.

How matching works under the hood

You do not need any of this to use the shortcuts, but it explains a few edge cases:

  • Keys are matched by physical position. Single-letter keys are matched by the key’s physical position on the keyboard (the browser’s event.code), not by the character it produces. This is deliberate: on macOS WebKit, holding Option transforms the character (for example, Cmd+Alt+N would otherwise produce ˜), so position-based matching keeps shortcuts working across keyboard layouts and modifier combinations.
  • First match wins. When a key matches more than one binding, the first binding in registration order whose when-clause is true runs; LeapMux then prevents the browser default and runs the command.

See also

  • Tabs & Layout — the tabs, tiles, and sidebars these shortcuts drive.
  • Terminals — terminal behavior and the macOS cursor-motion shortcuts.
  • File Browser — the directory tree refreshed and filtered by the Files shortcuts.
  • Settings & Preferences — the Preferences dialog and where account vs. browser settings live.
  • Running LeapMux — solo vs. distributed mode, which affects the “Open in External Editor” shortcut.
Last updated on