‹ Guides

The tmux config we use

The ~/.tmux.conf behind the screenshots on this site — tuned for a small screen, a coding agent, and a phone and a laptop attached at the same time.

Written

Why bother

tmux's defaults were written for a desktop: one client attached, a keyboard with a full modifier row, and a program whose output is text. On a phone every one of those is wrong. The screen is small, a second client is often attached from a laptop, and the thing running inside is increasingly a coding agent that speaks in colour, images and Shift+Enter — none of which survives a stock tmux intact.

This is the file we actually run, not a starter template: every tmux screenshot on this site was taken through it. Copy it whole, or take the parts you want — every line carries a comment saying what it buys, and the sections below explain the choices that are not obvious.

~/.tmux.conf
##### Terminal capabilities #####

# Terminal type + 24-bit true color -- without RGB, gradients show banding
set  -g default-terminal "tmux-256color"
set -as terminal-features ",*:RGB"

# RGB tells tmux the OUTER terminal speaks truecolor; COLORTERM is what the
# programs INSIDE check (nvim, bat, fzf, delta) before sending 24-bit color.
# tmux never passes that variable through by itself, so pin it
set-environment -g COLORTERM truecolor

# Inline images (sixel, kitty, imgcat) -- otherwise tmux drops them
set  -g allow-passthrough on
set -as terminal-features ",*:sixel"

# Copying inside tmux lands on the phone clipboard (OSC 52)
set  -g set-clipboard on

# ...and over mosh too. tmux sends that OSC 52 with an empty target -- 52;; --
# and mosh forwards only 52;c;, so without this line a copy made inside tmux
# reaches tmux's own paste buffer and never leaves the server. Keep the %p1:
# a format that skips to %p2 loads without complaint and then sends nothing
set -ag terminal-overrides ',*:Ms=\E]52;%p1%sc;%p2%s\a'

# Let Ctrl+Shift+..., Shift+Enter etc. pass through tmux, in CSI u form
# (Claude Code relies on this for Shift+Enter newlines)
set  -s extended-keys on
set -as terminal-features ",*:extkeys"
set  -g extended-keys-format csi-u

# Don't sit on Esc -- the default 500ms wait ruins Alt combos
set -sg escape-time 10

# Focus events and mouse (hold Shift in the terminal for local selection)
set  -g focus-events on
set  -g mouse on

# Forward window titles so the terminal app can name its tabs
set  -g set-titles on
set  -g set-titles-string "tmux · #{pane_title}"

# Scrollback depth -- agents produce a lot of output, give them room
set  -g history-limit 100000

##### Mobile / multiple clients attached at once #####

# Size windows to the most recently active client -- with phone and desktop
# both attached, whoever is typing gets their screen size; a small client
# never locks the layout down
set  -g window-size latest
setw -g aggressive-resize on

# Number windows and panes from 1, refill gaps on close -- 1 is easier to
# reach than 0 on a phone keyboard
set  -g base-index 1
setw -g pane-base-index 1
set  -g renumber-windows on

# Killing the current session jumps to the next one instead of detaching --
# reconnecting from a phone is a pain
set  -g detach-on-destroy off

# Keep messages up long enough to read on a small screen
set  -g display-time 2000
set  -g display-panes-time 2000

##### Code agent alerts #####

# Claude Code and friends ring the bell when done -- no screen flash, just
# a bold ! flag on that window in the status line
set  -g bell-action any
set  -g visual-bell off
setw -g monitor-bell on

# New output in a background window -> # flag in the status line, so you
# can tell at a glance which agent is running and which has stopped
setw -g monitor-activity on
set  -g visual-activity off

##### Colors #####

# bg=default lets the terminal's own background show through, and the
# accents (session name, prefix hint, activity) come from its palette.
# Anything that has to stay readable on every attached client uses fixed
# 256-colour values instead, which look the same in every theme

# Status line on top, away from Claude Code's input box at the bottom
set  -g status-position top
set  -g status-interval 5
set  -g status-style "bg=default,fg=default"

# Left: session name; right: ^B hint only while the prefix is held
set  -g status-left "#[fg=colour5,bold] #S #[default]"
set  -g status-left-length 20
set  -g status-right "#{?client_prefix,#[fg=colour3 bold]^B ,}"
set  -g status-right-length 20

# Window list: the current window is a blue block carrying its own white
# text (5.8:1 in any theme), the others a mid-gray; bell(!) red,
# activity(#) yellow
setw -g window-status-format " #I #W#{?window_flags,#{window_flags}, } "
setw -g window-status-style "fg=colour245"
setw -g window-status-current-format " #I #W "
setw -g window-status-current-style "bg=colour26,fg=colour231,bold"
setw -g window-status-bell-style "fg=colour203,bold"
setw -g window-status-activity-style "fg=colour3"

# Pane borders, messages, copy-mode selection -- one gray, one blue
set  -g pane-border-style "fg=colour243"
set  -g pane-active-border-style "fg=colour32"
set  -g message-style "fg=colour32,bold,bg=default"
# tmux's own command prompt is an opaque black bar -- match the messages
set  -g message-command-style "fg=colour32,bold,bg=default"
set  -g mode-style "bg=colour26,fg=colour231"
set  -g display-panes-colour colour243
set  -g display-panes-active-colour colour32
setw -g clock-mode-colour colour32

##### Key bindings #####

setw -g mode-keys vi

# Splits and new windows inherit the current directory -- no need to cd
# back to wherever the agent is working
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
bind c new-window   -c "#{pane_current_path}"

# Alt+number jumps straight to a window, no prefix -- one less chord on
# a phone keyboard
bind -n M-1 select-window -t 1
bind -n M-2 select-window -t 2
bind -n M-3 select-window -t 3
bind -n M-4 select-window -t 4
bind -n M-5 select-window -t 5

# prefix + hjkl to move between panes
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

##### Copy & paste #####

# prefix + v also enters copy mode (easier to remember than [)
bind v copy-mode

# Copy mode: v to select, y to copy (via OSC 52 to the phone/local clipboard),
# Y grabs the whole line, Ctrl+v toggles rectangle selection
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel
bind -T copy-mode-vi Y send -X copy-line-and-cancel
bind -T copy-mode-vi C-v send -X rectangle-toggle

# Mouse drag copies but stays in copy mode at the same spot -- no jump back
# to the bottom, so you can keep reading agent scrollback; q exits
bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection

# Double-click copies a word, triple-click copies the line. Word boundaries
# are whitespace only, so a double-click grabs a whole file path or URL out
# of agent output in one go
set -g word-separators " "
# Keep the `run -d0.3`: entering copy mode and copying in the same command
# fills tmux's buffer but sends no OSC 52, so nothing reaches the clipboard.
# -H, not -M: -M passes the click on and stretches the selection past the word
bind -n DoubleClick1Pane copy-mode -H \; send -X select-word \; run -d0.3 \; send -X copy-selection-and-cancel
bind -n TripleClick1Pane copy-mode -H \; send -X select-line \; run -d0.3 \; send -X copy-selection-and-cancel
bind -T copy-mode-vi DoubleClick1Pane send -X select-word \; send -X copy-selection-and-cancel
bind -T copy-mode-vi TripleClick1Pane send -X select-line \; send -X copy-selection-and-cancel

# prefix + ] pastes the newest copy (tmux default); prefix + P opens the
# buffer list to paste something copied earlier
bind P choose-buffer

# prefix + t: popup scratch terminal for one-off commands without touching
# the agent's layout; closes on exit
bind t display-popup -E -w 80% -h 75% -d "#{pane_current_path}"

# prefix + r: reload config
bind r source-file ~/.tmux.conf \; display "reloaded ~/.tmux.conf"

What tmux hides from your terminal

A multiplexer sits between the program and Term, and a stock one is conservative about what it passes on. allow-passthrough on is what lets inline images reach the screen — sixel, kitty graphics and imgcat all arrive as escape sequences tmux would otherwise swallow. terminal-features ",*:RGB" restores 24-bit colour, without which every gradient in a TUI turns to banding. And set-clipboard on is what makes y in copy mode land on the phone's clipboard over OSC 52, with no helper binary installed on the server.

RGB only tells tmux itself that Term can draw 24-bit colour. The programs inside decide for themselves by reading COLORTERM, and tmux never passes that variable through: a server started from a plain shell leaves nvim, bat and delta falling back to 256 colours, which looks like a washed-out theme rather than an error. set-environment -g COLORTERM truecolor pins it for every pane opened after it. Term also sets it on a running tmux server when it connects; the line keeps the config right from any terminal.

The three extended-keys lines are the newest of the group and the easiest to miss. They let Ctrl+Shift+…, Shift+Enter and friends through in CSI u form instead of flattening them into their plain equivalents. Claude Code uses Shift+Enter for a newline inside a prompt; without these lines that keystroke arrives as a bare Enter and submits the prompt half-written.

The terminal-overrides line under set-clipboard exists for one specific pairing: tmux running inside a mosh session. An OSC 52 names which clipboard it means, and tmux leaves that name empty — it sends 52;;. mosh matches the target as a literal 52;c;, so it forwards nothing, and a copy made in tmux lands in tmux’s own paste buffer and stops there. The override pins the name to c. Keep the %p1 in the format even though its value is thrown away: a version that skips straight to %p2 loads without complaint and then sends nothing at all.

A phone and a laptop at once

Attach twice and stock tmux sizes every window to the smallest client — so the moment a phone joins, a 200-column laptop is squeezed down to 56 and stays there. window-size latest hands the size to whichever client was last active instead: type on the laptop and it is the laptop's screen, pick up the phone and it re-flows for the phone. aggressive-resize does the same for individual windows.

detach-on-destroy off is there for the same reason. Kill the last pane of a session and stock tmux drops you out to the far-end shell — which, from a phone, means the connection you just spent twenty seconds establishing. Jumping to the next session instead keeps you inside.

Which agent is still working

An agent that finishes a turn rings the bell. With monitor-bell on and visual-bell off, that becomes a bold red ! beside the window's name in the status line rather than a flash across the screen you are reading, and monitor-activity puts a yellow # on any window that produced output at all. On a phone, where exactly one window is visible at a time, the status line is the only way to tell which of four agents is still thinking and which is waiting on you.

A status line that reads on any theme

bg=default lets Term's own background show through the status line, and the accents (the session name, the ^B hint, the activity flag) are palette colours, so they re-theme along with Term. But following the palette is not the same as staying readable. A palette colour keeps its role, not its brightness: colour8 is "bright black" in every theme, a dim grey on a dark one. And the old trick for the current window, reverse, made its contrast a lottery: 2.2:1 on a black-background palette, fine on a pastel one. With a phone and a laptop attached, the two palettes are not even the same.

So everything you have to read uses fixed 256-colour values, which draw the same in every terminal. The current window is a blue block that carries its own white text (colour26 behind colour231, 5.8:1 in any theme); the other window names are a mid-grey that reads on light and dark alike; borders, messages, the command prompt and the clock share one blue. tmux 3.7 can tell a light client from a dark one (#{client_theme}), but only when the terminal reports it, and over mosh that report never arrives, so nothing here depends on it.

Keys that suit a thumb

Every binding here removes a chord or a trip across the keyboard. | and - split the way they look. prefix + hjkl moves between panes without going near the arrow keys. prefix + t opens a popup scratch terminal over the current pane — the phone answer to “I need to run one command without disturbing the agent’s layout” — and it closes when you exit.

Alt+1 … Alt+5, with no prefix

Switching windows is the most frequent thing you do in tmux and the most expensive on a phone: prefix, then a digit, is two chords deep. bind -n drops the prefix entirely, and Term’s key bar has a sticky alt, so it comes down to two taps.

Splits that stay where you are

The -c "#{pane_current_path}" on the split and new-window bindings opens the new pane in the directory you are already in. Without it every split starts at $HOME, and the first thing you type on a phone keyboard is a long cd.

Copying agent output

The copy bindings are tuned for picking things out of what an agent printed. Double-click copies a word, and because word-separators is just a space, a "word" is a whole file path or URL: one click instead of a careful drag. Triple-click takes the line. A mouse drag copies and leaves you in copy mode where you were, instead of snapping back to the bottom, so you can keep reading back through the scrollback; q leaves. prefix + P opens the list of earlier copies.

In the double- and triple-click bindings, keep the run -d0.3. Entering copy mode and copying in the same command fills tmux's paste buffer but sends no OSC 52, so the copy never reaches the clipboard, on any connection. The short delay lets the mode change settle first, which is why tmux's own default binding has one too.

Installing it

  1. Copy the file above and save it as ~/.tmux.conf on the server — not on the phone. tmux reads it where the session runs.
  2. Load it once from a shell prompt, with the command below.
  3. After that, prefix + r reloads it following every edit.
the first load, from a shell
tmux source-file ~/.tmux.conf

That middle step looks redundant next to the reload binding, and it is not: prefix + r cannot perform the first load. The binding only exists once the file has been read, and a tmux server reads its config when it starts — which, the first time, was before the file existed. The one reload you cannot do with the shortcut is the one that installs the shortcut. Note also that source-file is a tmux command rather than a shell one: that is why it needs tmux in front of it here, and nothing in front of it inside the config.

Two things to check before any of it takes: this needs tmux 3.5 or newer, because an older build answers “unknown option” at start-up and the setting silently does nothing — tmux -V tells you which you have. And on Debian and Ubuntu the tmux-256color terminfo entry the first line selects ships in the ncurses-term package rather than with tmux itself, so install that alongside it.

See also: Using tmux from a phone · SSH auto-resume · FAQ