tmux — Keybindings¶
All bindings use the default prefix Ctrl + b.
Daily Workflow¶
| Keybinding | Action |
|---|---|
prefix + R |
Reload ~/.tmux.conf |
prefix + Space |
Open the tmux popup menu (mnemonic: "menu") |
prefix + g |
Open sesh picker |
prefix + T |
Open sesh picker via television (tv) |
prefix + O |
Open sesh built-in picker |
prefix + S |
Jump to the last sesh session (status-bar message if none) |
prefix + 9 |
Open scode for current dir (repo-aware coding-agent layout — nvim 75% | agent 25% + btop monitor; idempotent, switches to existing session if already created) |
prefix + 0 |
Lightweight sesh session at git root of current dir (no nvim/agent layout — symmetric counterpart to prefix + 9) |
prefix + N |
New session (prompts for name) |
prefix + X |
Kill session (with confirmation) |
prefix + M-x |
Kill current session and exit tmux client; clears auto-restore only if this was the last session |
prefix + M-Q |
Kill all sessions cleanly and forget tmux-continuum auto-restore |
prefix + W |
Kill window (with confirmation) |
prefix + r |
Renumber windows (close gaps left by killed windows) |
prefix + M |
Move current window to another session (prompts for session[:index]) |
prefix + B |
Break current pane into a new window and move it to a session (tab tear-out) |
prefix + A |
Link current window into another session (window appears in both) |
prefix + E |
Explode — break every pane in current window into its own window (same session). For switching from wide-screen multi-pane to mobile/SSH single-pane |
prefix + d |
Detach |
prefix + t |
Show tmux clock mode |
prefix + M-c |
Switch theme to Catppuccin (top status bar) |
prefix + M-t |
Switch theme to tmux2k (bottom status bar) |
Panes and Windows¶
| Keybinding | Action |
|---|---|
Ctrl + 1..9 |
Switch to window 1–9 (requires CSI-u terminal: Ghostty, Alacritty, Kitty) |
Ctrl + 0 |
Lightweight sesh session at repo root for current pane (mirrors prefix + 0) |
Ctrl + h/j/k/l |
Move between panes — crosses into Neovim splits (vim-tmux-navigator)* |
Ctrl + \ |
Focus previous pane/split (vim-tmux-navigator)* |
prefix + h/j/k/l |
Move between panes (fallback, tmux-only) |
prefix + H/J/K/L |
Resize panes (5 cells, repeatable) |
prefix + M-h/j/k/l |
Fine resize panes (1 cell, repeatable) |
prefix + + |
Set current pane to 75% width |
prefix + \| |
Split left/right (vertical divider) |
* Ctrl + h/j/k/l and Ctrl + \ (vim-tmux-navigator) are gated on
the chezmoi enableVimMode prompt (default true). When false,
those bindings are omitted — Ctrl+L reaches the inner shell as
clear-screen, Ctrl+H as backspace, etc. Use prefix + h/j/k/l (kept
regardless) or prefix + Arrow for pane navigation. See
docs/this_repo/vim-mode.md.
| prefix + - | Split top/bottom (horizontal divider) |
| prefix + c | New window in current path |
| prefix + x | Kill pane (with confirmation) |
| prefix + W | Kill window (with confirmation) |
| prefix + r | Manually renumber windows (close index gaps) |
| prefix + z | Toggle pane zoom |
| prefix + { | Swap pane with previous (left/up) |
| prefix + } | Swap pane with next (right/down) |
| prefix + m | Mark current pane (built-in; one mark globally; needed by Join in menus) |
| prefix + ! | Break current pane into a new window in same session (built-in) |
| prefix + [ | Enter copy mode |
Ctrl+1..9 and Ctrl+0 require CSI-u terminal support. Ghostty/cmux sends these natively. Alacritty needs explicit keyboard.bindings (managed by this repo in dot_config/alacritty/alacritty.toml). Legacy terminals (Terminal.app, plain SSH) cannot send these — use prefix + number instead.
Swap-pane swaps the content while keeping the sizes. So if you have a 75%/25% split and swap, the left pane's content moves to the right (25%) and vice versa — the proportions stay fixed.
Layouts¶
| Keybinding | Action |
|---|---|
M-1 |
Even horizontal layout |
M-2 |
Even vertical layout |
M-3 |
Main horizontal layout |
M-4 |
Main vertical layout |
M-5 |
Tiled layout |
prefix + E |
Spread panes evenly (built-in) |
M-1 through M-5 are tmux built-ins — no prefix needed; just hold Meta (Alt/Option) and press the number.
macOS terminal requirement: Option must send Meta/Esc+ for
M-bindings to work. Ghostty/cmux:macos-option-as-alt = left(managed by this repo indot_config/ghostty/config). Alacritty:window.option_as_alt: OnlyLeft. iTerm2: Profiles > Keys > Left Option Key > Esc+. See docs/tools/ghostty.md.
Floating Pane (tmux-floax)¶
| Keybinding | Action |
|---|---|
prefix + F |
Toggle floating pane (80% width/height, persistent float session) |
prefix + P |
Open floax popup menu |
prefix + \`` | One-shot **popup shell** at current pane path (each invocation = fresh shell, exits onexit`/Ctrl-D) |
Two flavours of popup shell, pick by use case:
- **
prefix + \``** — quick-and-forget. Run acurl, checkdf -h`, eyeball a file, exit. Each open is a clean shell. No history across opens. Good for "I just need to type one command without leaving my pane layout". prefix + F(floax) — scratchpad. Thefloatsession persists, so when you re-toggle the popup, your previous shell history, environment, and even running processes are still there. Good for "I'm iterating on something and don't want to lose the context".
One-shot Popups¶
These open display-popup -E at #{pane_current_path}; the popup closes when the inner command exits. Unlike floax, no session persists.
| Keybinding | Action |
|---|---|
prefix + G |
lazygit popup |
prefix + T |
sesh picker (television) |
prefix + O |
sesh built-in picker |
prefix + U |
CLI tools picker (tv tools) |
prefix + u |
URL picker (tmux-fzf-url) |
prefix + a |
Live coding-agent panes picker (tv agent-panes) — see agent pane discovery |
prefix + M-a |
Agent quota wakeup dashboard (tv agent-wakeup) — quota waits, reset timers, scheduled continue |
When to use which:
- floax (
prefix + F) — repeated quick-access shell, want history preserved (notes, scratch math, long-running curl). - `prefix + `` popup shell — quick command in a fresh shell, exit and forget.
- One-shot tool popup (
G/...) — start a TUI tool, do work, exit cleanly.
Help / Discovery (no more memorizing)¶
The built-in prefix + ? (a wall-of-text list-keys -N dump) is replaced with tmux-fzf: an fzf popup over every binding (including user-defined), fully fuzzy-searchable.
| Keybinding | Action |
|---|---|
prefix + ? |
tmux-fzf: top-level fuzzy picker (keybindings / sessions / windows / panes / commands / processes / clipboard) |
prefix + C-? |
Plain list-keys -N (fallback if tmux-fzf is missing) |
prefix + / |
Built-in: prompt for a key, show what it's bound to (single-key lookup) |
prefix + Space then ? |
Curated cheatsheet rendered with glow (source: dot_config/tmux/cheatsheet.md) |
prefix + Space then / |
tmux-fzf keybinding picker directly (skips the category menu) |
Three layers of "I forgot the key" recovery:
- Curated (
prefix + Space): grouped popup menu with the high-traffic top-level rows + submenus. - Searchable (
prefix + ?→ tmux-fzf): fuzzy-search across the full binding list. - Reference (
prefix + Space→?): glow-rendered markdown cheatsheet (this file'scheatsheet.mdsibling), good for browsing while learning.
Why three? The popup menu is fastest once you know roughly what you want; tmux-fzf is for "I know it exists somewhere"; the cheatsheet is for "what's even possible?". Pick whichever matches your current uncertainty.
Copy Mode (Vim-Style)¶
Enter with prefix + [. Navigate with vim keys, then:
| Key | Action |
|---|---|
v |
Begin character selection (visual mode) |
V |
Select entire line |
C-v |
Toggle rectangle/block selection |
y |
Yank selection to system clipboard |
/ |
Search forward |
? |
Search backward |
n/N |
Next/previous search match |
g/G |
Jump to top/bottom |
C-u/C-d |
Half-page up/down |
{ / } |
Jump to previous / next prompt (needs OSC 133 — see OSC 133) |
M-[ / M-] |
Jump to previous / next command output start (needs OSC 133) |
q or Escape |
Exit copy mode |
Mouse in copy mode (vim-idiomatic: select first, then copy)¶
Drag-select highlights only — it does not copy (MouseDragEnd1Pane → stop-selection). This matches the visual-select-then-yank habit and avoids clobbering the clipboard on every stray drag. To copy the highlighted selection, do any of:
- press
y(copy-selection-and-cancel), - right-click the selection (
MouseDown3Panedetectsselection_presentand copies), - or just double-click a word (selects and copies in one gesture).
Every copy path uses copy-selection-and-cancel, so copying also exits copy mode (no being stranded in copy mode needing q). Right-clicking when there is no active selection still opens the pane context menu (see below) — the two are mutually exclusive states, so there is no conflict. All copies honor set-clipboard on / OSC 52, so they reach the local clipboard even when this tmux runs on a remote host over SSH.
Command-boundary keys ({ } M-[ M-]) rely on OSC 133 markers emitted by dot_config/zsh/tools/02_shell_integration.zsh. In a pane running a non-zsh shell or one that opted out via DISABLE_OSC133=1, they are silent no-ops.
Right-click menus¶
Right-click opens a context menu depending on where you click. Each menu also has a keyboard equivalent under prefix:
| Target | Mouse | Keyboard | Menu items |
|---|---|---|---|
| Pane body | MouseDown3Pane |
prefix + M-p |
Split h/v, swap up/down/left/right, zoom, resize 75% / even, mark, swap marked, join marked here (h/v), send pane to window…, break to new window, copy mode, respawn, kill |
| Window tab | MouseDown3Status |
prefix + M-w |
Swap left/right, move/link to session, merge into other window as pane (h/v), even layout (horizontal / vertical / tiled), kill window, renumber windows, rename, new window, bookmarks (⭐/📌/🔖/🚩), clear agent status |
| Status-left (session name) | MouseDown3StatusLeft |
prefix + M-s |
Next/prev/choose/rename session, copy session name, move current window, new session/window, kill session, kill session & exit (clean), kill all sessions (clean), forget auto-restore |
Our bindings use display-menu -O so the menu stays open after the mouse button is released — pick an item or press Escape to dismiss. (tmux's defaults omit -O and dismiss on release, which makes the menu unusable.)
MouseDown3Paneis overloaded by context. When you right-click while a copy-mode selection is active, it copies that selection (copy-selection-and-cancel) instead of opening the menu — see Mouse in copy mode. The pane menu only appears when there is no active selection, so the keyboard equivalent (prefix + M-p) and the menu-body duplication rule below are unaffected.
After break / send / merge / join, a status-bar message reports what happened (e.g. Broke pane out to window 4 (zsh), Merged into 1).
Why two implementations of the same menu?¶
The menu body for each of the three right-click menus is intentionally duplicated between the inline mouse binding (in dot_config/tmux/keybindings.conf.tmpl) and the corresponding script (dot_config/tmux/menu-pane.sh / menu-window.sh / menu-status-left.sh):
- The mouse path MUST stay inline. A
display-menulaunched indirectly viarun-shellfrom aMouseDown3*binding loses its "opened from a mouse key binding" status, and the queued mouse-release event dismisses it (flash-and-gone, or pops to bottom-right). Seepitfalls/tmux-submenu-flash-and-bottom-right.md. - The keyboard path doesn't have that queued mouse event, so a script is safe — and keeps
keybindings.confshort.
Both surfaces carry a "sync me" comment pointing at the other. When editing menu items, update both copies in the same commit.
display-menu height limit¶
display-menu does not paginate. If the menu is taller than the terminal, the entire popup is silently suppressed (no error, nothing in tmux show-messages). Both the mouse and keyboard triggers are equally affected — the keyboard variant does NOT rescue the menu on small terminals. The window-tab menu is currently 23 rows so it will not appear on terminals smaller than ~26 rows. See pitfalls/tmux-display-menu-silent-fail.md.
Clean quit and tmux-continuum auto-restore¶
The repo enables @continuum-restore 'on' (in dot_config/tmux/common.conf.tmpl), so tmux-continuum auto-restores the last saved session every time the tmux server starts with no existing sessions. It cannot distinguish "user intentionally killed everything" from "crashed / rebooted" — both look identical (server gone → restore on next start).
Two paths route through clean-quit wrappers that clear the resurrect last symlink before the server dies, so the next start has nothing to restore:
| Path | Wrapper | When |
|---|---|---|
prefix + M-x ("Kill session & exit") |
kill-session-exit.sh |
Only when this is the last surviving session (so the server is about to die). With other sessions remaining the server keeps running — no restore is triggered. |
Right-click status-left → "Kill all sessions (clean)" (mouse or prefix + M-s) |
kill-server-clean.sh |
Always. |
| Popup menu → Session → "Kill all sessions (clean) (Q)" | same | Always. |
prefix + M-Q or shell tmux-kill-clean |
same | Always. |
Right-click status-left → "Forget auto-restore", popup menu → Session → "Forget auto-restore", or shell tmux-forget-last |
resurrect-forget.sh |
Clears only the last symlink; does not kill the server. |
Other kill paths (prefix + x kill-pane, prefix + W kill-window, plain prefix + X kill-session) deliberately do NOT touch the symlink — they're not "I'm exiting tmux" actions, so leaving the server up means no restore is triggered.
Crash recovery is unaffected: historical resurrect snapshots stay on disk under ~/.local/share/tmux/resurrect/. After a crash, the next start auto-restores from last as before, OR if you've cleaned it, run prefix + Ctrl-r to manually pick from history.
See pitfalls/tmux-continuum-restores-on-intentional-quit.md for the full debugging trail and why we keep @continuum-restore 'on' instead of turning it off entirely.
URL Opening¶
| Keybinding | Action |
|---|---|
prefix + u |
Open fzf popup listing all URLs in the pane (tmux-fzf-url) |
In copy-mode (after selecting text with v):
| Key | Action |
|---|---|
o |
Open selected URL/file in default browser/app (tmux-open) |
C-o |
Open selection in $EDITOR |
S |
Search selection in Google (tmux-open, configurable via @open-S) |
Typical workflow: prefix + u for quick URL browsing; prefix + [ then select + o for precise URL opening.
Capture Pane¶
| Keybinding | Action |
|---|---|
prefix + y |
Copy visible pane content to system clipboard |
prefix + Y |
Copy full scrollback to system clipboard |
prefix + C-y |
Open scrollback in fzf, select lines to copy (Tab=multi) |
prefix + M-y |
Copy last command's output to clipboard (Warp-style, needs OSC 133 — see OSC 133) |
prefix + M-i |
Copy last command's input line (prompt + typed command) to clipboard (needs OSC 133) |
Cross-platform: pbcopy on macOS, xclip/xsel on Linux. OSC 52 also works for the vim-style y yank (even over SSH).
Moving Windows / Panes Across Sessions¶
Like dragging a browser tab into a new window — but tmux can do it at three different granularities (whole window, pane → new window, pane → existing window as split). All cross-session/cross-window targets are picked from a choose-tree picker (live preview, fuzzy-search), not typed into a prompt. See the picker-over-prompt design rule below for why.
Window-level (whole tab)¶
| Key | Underlying command | Effect |
|---|---|---|
prefix + M |
choose-tree -Zs … move-window -s '#{window_id}' -t '%%' |
Cut current window out of this session and paste into target (session picker) |
prefix + A |
choose-tree -Zs … link-window -s '#{window_id}' -t '%%:' |
Link (not copy): same window appears in both sessions; edits stay in sync. unlink-window removes one side without killing |
prefix + W |
kill-window |
Kill current window (with confirmation) |
prefix + r |
move-window -r |
Renumber windows in current session — closes index gaps. renumber-windows on (set in common.conf) auto-renumbers when a whole window is destroyed, but kill-pane on multi-pane windows or shell-driven exits can still leave gaps; this binding is the manual top-up. |
Pane → new window¶
| Key | Underlying command | Effect |
|---|---|---|
prefix + ! |
break-pane (built-in) |
Break current pane into a new window in the same session |
prefix + B |
choose-tree -Zs … break-pane -s '#{pane_id}' -t '%%' |
Break + move to chosen session in one step (tab tear-out, session picker) |
prefix + E |
~/.config/tmux/break-all-panes.sh |
Explode — break every pane in current window into its own window (same session). Source window keeps the first pane; others become sibling windows inserted right after, named after each pane's current command. Use case: continuing a wide-screen multi-pane layout on mobile/SSH where one pane per window is easier than zooming with prefix + z. Also available via right-click window menu → "Break all panes → windows". |
| Right-click pane → "Break to new window" | break-pane |
Same as prefix + !, but reports the new window index in the status bar |
Pane → existing window (as a split)¶
These all use tmux's join-pane, which moves a pane between windows. join-pane always operates on individual panes, not whole windows — there is no "merge two windows wholesale" command. The two-step "mark + join" idiom is the canonical workflow:
- Go to the source pane (the one you want to move).
- Press
prefix + mto mark it. tmux remembers exactly one marked pane globally; the marked pane gets a coloured border. - Switch to the destination window.
- Right-click any pane → "Join marked pane here (h-split)" or "(v-split)". The marked pane jumps over and becomes a split.
Alternative entry points to the same join-pane command:
| From | Action | Effect |
|---|---|---|
| Right-click pane → "Join marked pane here (h/v-split)" | join-pane -h / -v |
Pull the marked pane into this window as a split |
| Right-click pane → "Send pane to window…" | choose-tree -Zw … join-pane -h -s '#{pane_id}' -t '%%' |
Push this pane out to another window as a split (no mark needed; pick target from window tree picker) |
| Right-click window tab → "Merge into other window (as pane, h/v-split)" | choose-tree -Zw … join-pane -h -s '#{window_id}' -t '%%' |
Move this window's active pane into another window as a split. If the source window has only one pane, it disappears (effectively merging the whole window). With multiple panes, only the active one moves. |
| popup menu → Session → "Join marked here (h/v)" / "Send pane to…" | same as right-click | Same actions, keyboard-driven |
Why no top-level
prefix +key for join-pane / send-pane? All single-letter capital slots are taken (H/J/K/L= resize,S= sesh-last,W= kill-window,M/N/B/A= window ops). Rather than rebind something else, these live in the right-click menus and the popup menu's Session submenu. The mark-and-join workflow is mostly mouse-friendly anyway.
Tip: prefix + s (built-in choose-tree) shows live previews — handy for previewing sessions before invoking M/B/A, though those bindings now open their own pickers so the standalone preview is mostly for casual browsing.
Design note: pickers over prompts¶
All cross-window/cross-session bindings in this repo use choose-tree -Zw (window picker) or -Zs (session picker), not command-prompt. Two reasons:
- Correctness for
target-panecommands. tmux'sjoin-pane,swap-pane,move-paneparse-t TARGETas a pane — a bare integerNmeans "pane index N in the current window", not "window N". Users typing1thinking "window 1" silently target the wrong pane (often the source pane itself, producingSource and target panes must be different).choose-tree -Zwreturnssession:window, which the parser unambiguously resolves to that window's active pane. Full debugging trail inpitfalls/tmux-join-pane-numeric-target-pane-not-window.md. - UX. Live preview, fuzzy-search, and zero memorisation of session names beat a free-form prompt even when both are technically safe (e.g.
move-window -taccepts session-level targets without ambiguity).
Source pane/window is always pinned with -s '#{pane_id}' or -s '#{window_id}' rather than relying on "current pane / current window" — #{pane_id} resolves at click time against the client's active pane, which may not be the right-clicked tab on status-bar menus.
When adding a new keybinding/menu row that targets a window or session: reach for choose-tree -Zw / -Zs first; only fall back to command-prompt if no picker fits (free-form rename, brand-new session name, etc.).
Built-in tmux Keys Still Available¶
| Keybinding | Action |
|---|---|
prefix + s |
Choose session tree |
prefix + w |
Choose window tree |
prefix + q |
Show pane numbers |
prefix + , |
Rename window |
prefix + $ |
Rename session |
prefix + ? |
tmux-fzf: fuzzy-search all keybindings (rebound from list-keys) |
prefix + C-? |
Built-in list-keys -N (fallback) |
prefix + / |
Prompt for a key and show what it is bound to |
Popup Menu (prefix + Space)¶
The popup menu is bound to prefix + Space and is generated by a script (~/.config/tmux/menu.sh) rather than defined inline in keybindings.conf. Two reasons:
- Quoting. tmux's command parser stops at literal
;,{,}even inside nested quotes. With ~50 menu rows full of fzf binds and shell one-liners, escaping inkeybindings.confwas fragile and broke silently. - Height-aware trimming.
display-menudoes not paginate. If the menu is taller than the terminal, the entire popup is suppressed with no error. The script reads#{client_height}and emits one of three tier sets so the menu always fits.
Tiers:
| Terminal height | Top menu shows |
|---|---|
| Any (Tier 0) | Last window/pane, Choose win/sess, Pane #s, Sesh picker, Lazygit (~9 rows) |
| ≥ 14 (Tier 0+2) | + submenu launchers (→ Layouts/Session/Sesh+/Popups/Theme/System), Cheatsheet, Search keys |
| ≥ 22 (Tier 0+1+2) | + New window, Split \|/-, Zoom |
Submenus are separate scripts (menu-layouts.sh, menu-session.sh, menu-sesh.sh, menu-popups.sh, menu-theme.sh, menu-system.sh), invoked from rows via run-shell. Each is an independent display-menu so quoting context resets.
Historical note: an earlier debugging round assumed
prefix + Spaceandprefix + Enterwere broken because ofextended-keys/csi-ukeysym encoding (tmux/tmux#4571, #4147, #4959, #4984), and the menu was moved toprefix + eas a workaround. That diagnosis was wrong. The actual failure was that the inline 50-row flat menu was taller than the terminal, and tmux silently suppresses oversized menus perman tmux("If the menu is too large to fit on the terminal, it is not displayed."). BothSpaceandEnterwere always bindable; the keysym story was a red herring. Full debugging trail inpitfalls/tmux-display-menu-silent-fail.md. The menu was bound to bothSpace(canonical) ande(alias) for a while; theealias has since been removed to freeeas a sub-menu mnemonic (e.g. "Even layout: horizontal" in the window right-click menu). The canonical binding stays onprefix + Space.
Top-menu accelerator keys¶
Accelerator keys match the standalone prefix + key bindings wherever possible — pressing c inside the menu does the same thing as prefix + c outside it.
| Key | Row | Tier |
|---|---|---|
Tab |
Last window | 0 |
P |
Last pane | 0 |
w |
Choose window tree | 0 |
s |
Choose session tree | 0 |
q |
Show pane numbers | 0 |
g |
Sesh picker | 0 |
G |
Lazygit popup | 0 |
c |
New window | 1 (h ≥ 22) |
\| |
Split left/right | 1 |
- |
Split top/bottom | 1 |
z |
Zoom toggle | 1 |
L |
→ Layouts submenu | 2 (h ≥ 14) |
S |
→ Session submenu | 2 |
E |
→ Sesh+ submenu | 2 |
o |
→ Popups submenu | 2 |
T |
→ Theme submenu | 2 |
Y |
→ System submenu | 2 |
? |
Glow cheatsheet popup | 2 |
/ |
tmux-fzf keybinding picker | 2 |
Submenu rows¶
Source of truth: dot_config/tmux/executable_menu-*.sh.
- Layouts (
L): Even h/v (1/2), Main h/v (3/4), Tiled (5), Resize 75% (+), Pane h/j/k/l, Swap{/}. - Session (
S): Rename session/window ($/,), New session (N), Move window (m), Break pane (r), Link window (K), Renumber (R), Join marked here h/v (j/J), Send pane to (s), Kill pane/window/session/server (x/W/X/Q). - Sesh+ (
E): TV picker (V), Built-in (O), Last sesh (U), scode here (9), CLI Tools tv (B). - Popups (
o): Lazygit (g), Shell (s), Floax scratchpad (f). - Theme (
T): Catppuccin (c), tmux2k (t). - System (
Y): Reload config (R), Install plugins (I), Update plugins (U), Detach (d), Clock (k).
Hard cap: the top menu is currently 14 rows at full height. Adding a 15th row will start failing on smaller terminals (mobile SSH, half-screen splits). Push lower-frequency items into a submenu instead, and re-test by shrinking the terminal vertically (heights 14 / 22 / 60).