This is a very long guide about setting up tmux “from scratch,” which is to say without any of the default keybindings or any default behavior. It’s mostly an excuse to demonstrate a simple ~/.tmux.conf, and to explain how you can set up tmux to do exactly what you want it to, and nothing more.

I like this, because it turns my ~/.tmux.conf into documentation about what my tmux can do, and how it works. If something isn’t in that file, it doesn’t exist. If I forget how to do something, I can just look at that file. I don’t need to remember any tmux defaults; I don’t need anything except my little file.

so how do you do it

tmux has a very neat feature that I love. That feature is “unbind all the keys.” That’s how I start my tmux config. I unbind all the keys, and then bind exactly the ones I want.

All it takes is one magical line:

$ cat ~/.tmux.conf
unbind-key -a

Just like that: no more splits. No more tabs. No more weird anything. We have made tmux into a blank slate, and we can now cherry-pick exactly what features we want it to have. Don’t want splits? Now the only way to get one is to run tmux split exsplitcitly. So just… don’t do that.

Now I’m lying a little bit, for rhetorical effect. For starters, there is one obvious key that is still bound, and that’s the “prefix key.”

The prefix key works like a “leader key” in vim. You press the prefix, and then the next key you press determines the command you want to run.

By default the prefix key is C-b, or “control-b.” I think this is a particularly bad keybinding – not only is b is one of the hardest to reach keys on your entire keyboard, but C-b is a fairly commonly used shortcut already – it moves your cursor back one character, in bash and zsh and any other readline-y thing you’re using.

So we have to change that.

$ cat ~/.tmux.conf
unbind-key -a
set-option -g prefix M-w

I picked M-w for this example, which means “alt-w” or “option-w,” because it’s easy (for me) to press using only my left hand. But the choice of prefix key is deeply personal: you should pick what feels right to you.

set-option does what it says, and -g makes the change “globally.” You can also change options on a “per-session” basis, but you know what? We’re not using sessions. So we’re not going to think about that.

set-option is very common, and has a short alias that is just set. So we’re going to use that for the rest of this explanation.

$ cat ~/.tmux.conf
unbind-key -a
set -g prefix M-w

And now I’m still lying to you, a little bit. Because tmux actually has multiple “keymaps.” We cleared the “normal” one; we cleared the keys that you press after pressing the prefix. But there are others. Let’s make that explicit:

$ cat ~/.tmux.conf
unbind -a -T prefix
unbind -a -T root
unbind -a -T copy-mode
unbind -a -T copy-mode-vi
set -g prefix M-w

The -T specifies which “key table” we’re using. By default, we were only unbinding the “prefix” table. I also switched to the shorter unbind instead of unbind-key.

Is that everything? How can we be sure? We can run this:

$ tmux list-keys

We can see that it prints nothing out.

Meanwhile, if we run:

$ tmux -f /dev/null list-keys

You can see the all of the “default” keybindings. There are a lot! (-f /dev/null just causes tmux to ignore our ~/.tmux.conf.)

Anyway, now we have a tmux that really doesn’t do anything.

let’s make it do something

The first keybinding I’m going to add is a very useful:

$ cat ~/.tmux.conf
unbind -a -T prefix
unbind -a -T root
unbind -a -T copy-mode
unbind -a -T copy-mode-vi
set -g prefix M-w

bind M-r source-file ~/.tmux-conf

Now we can press M-w M-r (our prefix, followed by option-r) to reload the config – no more killing tmux and re-starting it every time something changes. I find it easier to press M-w M-r than to press M-w r, since you don’t need to time the release of the modifier.

But okay. That’s nice and all, but what we really care about here is tmux’s “copy mode:” its text selection feature. But before we can bind keys for it, we have to learn a little bit of trivia.

There are two “key tables” in tmux for copy mode, one which by default contains emacs-style keybindings (called copy-mode) and one that contains vi-style keybindings (called copy-mode-vi).

To decide which key table to use when you enter copy mode, tmux checks the value of the $EDITOR environment variable. If it contains the string “vi,” then it uses the copy-mode-vi table. Otherwise it uses the copy-mode table.

But this doesn’t really matter, to us. The key tables are currently identical: they’re both empty. I don’t really like this implicit guessery on tmux’s part, so I’d rather just specify which table to use explicitly. And even though I use vi-style bindings, I’d rather just have a single keymap called copy-mode.

But.

Switching between the “emacs bindings” and the “vi bindings” actually does a little bit more than just change the keys you’re using.

It actually changes the way selections behave – since in emacs you have a cursor that’s “between” characters, but in vim you have a cursor that’s “on” characters.

Which of these behaviors is more natural to you depends entirely on what you’re used to. I personally prefer the vim behavior. I’m used to it; it’s familiar to me. It changes another thing too, which is when you’re in “line select mode,” it decides whether or not that includes the final newline in the selection or not. And I like it to include the final newline. So I use “vi mode,” like so:

$ cat ~/.tmux.conf
unbind -a -T prefix
unbind -a -T root
unbind -a -T copy-mode
unbind -a -T copy-mode-vi
set -g prefix M-w
set -g mode-keys vi # <-- eyes over here please

bind M-r source-file ~/.tmux-conf

Now we have to use the keymap called copy-mode-vi, not copy-mode, but that’s okay. If you prefer the emacs style, set mode-keys emacs.

But so far this doesn’t really matter: we have no way to enter copy mode, and even if we did there would be nothing for us to do once we got there.

So let’s add our first “real” keybindings.

$ cat ~/.tmux.conf
unbind -a -T prefix
unbind -a -T root
unbind -a -T copy-mode
unbind -a -T copy-mode-vi
set -g prefix M-w
set -g mode-keys emacs

bind M-r source-file ~/.tmux-conf

bind Space copy-mode

bind -T copy-mode-vi Escape send-keys -X cancel

Alright! From here on out, I’m going to skip the preamble when I cat ~/.tmux.conf, because it’s getting a little bit noisy.

$ cat ~/.tmux.conf
...

bind Space copy-mode

bind -T copy-mode-vi Escape send-keys -X cancel

So what does this do? We added two keybindings. bind Space copy-mode is short for bind -T prefix Space copy-mode. That means to enter copy mode, we have to press M-w Space.

But if all we’re using tmux for is copy-mode, we could instead create a root keybinding:

bind -T root M-Space copy-mode

Or, more concisely:

bind -n M-Space copy-mode

That way we don’t need to worry about the prefix key, and could bind it so something implausible like C-S-M-p or something that we would never hit by accident. But that choice is entirely up to you.

Once we’re in “copy mode,” there is exactly one thing we can do:

bind -T copy-mode-vi Escape send-keys -X cancel

Which is to press escape and leave copy mode.

Actually, if you test this, you’ll see that if you press escape you wait half a second and then leave copy mode.

This is dumb, and the reason it works this way is because terminals are dumb and terminal emulators are dumb and escape sequences are dumb. By default when you press escape, tmux will wait 500ms before it passes escape on to the program you’re using, just in case you’re actually entering an escape sequence.

You aren’t, so go ahead and throw this in your ~/.tmux.conf:

set -g escape-time 1

I won’t try explain this too much; googling “tmux escape-time” will give you lots of results that explain it in detail. Note that I use 1 instead of 0 but I don’t know why. I just know that I switched to 1 years ago after something didn’t work quite right with 0. But I didn’t write down what it was. Bad job, past Ian.

But okay, back to our keys:

$ cat ~/.tmux.conf
...

bind Space copy-mode

bind -T copy-mode-vi Escape send-keys -X cancel

This is already a tiny bit useful, even though it sounds really dumb. This is because in copy mode, tmux will stop redrawing the screen, and will buffer new output in the background until you leave copy mode. So if you’re running some long-running command that’s spewing a ton of output, you can enter copy mode to “pause” it, and take a breath.

But… why is that so complicated? What’s up with send-keys -X?

I don’t really know. I don’t have a good answer for you here.

I would expect something like:

bind -T copy-mode-vi Escape run-copy-mode-command cancel

But for some reason run-copy-mode-command is called send-keys -X.

Anyway, pausing output is not that useful. So let’s keep going.

I’m going to use the alias send from here on out, because send -X reads slightly better to me than send-keys -X.

$ cat ~/.tmux.conf
...
bind Space copy-mode

bind -T copy-mode-vi Escape send -X cancel
bind -T copy-mode-vi Up     send -X cursor-up
bind -T copy-mode-vi Down   send -X cursor-down
bind -T copy-mode-vi Left   send -X cursor-left
bind -T copy-mode-vi Right  send -X cursor-right

Now when we enter copy mode, we can use the arrow keys to move our cursor around. And of course you can bind hjkl instead of the arrow keys. You can bind whatever you want.

Now we’re cooking. We can move around, and if we try to position our cursor off the top of the screen, tmux will scroll up for us. Neat!

But not that neat. We don’t just want to move around. We want to be able to select and copy text, too.

$ cat ~/.tmux.conf
...
bind -T copy-mode-vi Escape send -X cancel
bind -T copy-mode-vi Up     send -X cursor-up
bind -T copy-mode-vi Down   send -X cursor-down
bind -T copy-mode-vi Left   send -X cursor-left
bind -T copy-mode-vi Right  send -X cursor-right

bind -T copy-mode-vi Space  send -X begin-selection
bind -T copy-mode-vi y      send -X copy-selection-no-clear
bind -T copy-mode-vi Enter  send -X copy-selection-and-cancel

After you press space, you can move the cursor around to “trace” the text you want selected selection. This is exactly like pressing v in vim, but since this is the single most common thing I do in copy mode, I bind it to space instead, because it’s easier to type. That’s actually the tmux default using vi-style keybindings, and I think it’s a good one (although somewhat confusing).

Once you have a selection, y copies it selection, and enter/return/whatever copies the selection and then exits copy mode. I usually use the latter, but in case I’m copying multiple nearby things, the former is nice to have too.

Alright! That gets us a lot of the way there. But… you might notice, depending on your terminal emulator, that those copy- commands don’t actually seem to work.

This is because they’re only copying to tmux’s internal copy buffer. It’s like copying text in vim – vim doesn’t know how to use the system clipboard. It has its own thing going on.

But… we don’t want that. That’s annoying. We want to just copy to the system clipboard. How do we get tmux to do that?

Well, it depends a little bit on your terminal emulator.

The way tmux wants to copy text is to print out a bunch of escape sequences, which your terminal emulator will see, and will understand, and will then use to put text in the system clipboard.

But not all terminal emulators support that. If you’re using iTerm on a Mac, this will just work. If you’re using Terminal.app, it will not. tmux checks if the terminal emulator you’re running it in supports these escape sequences, and won’t print them if it doesn’t think you can handle it. You could force it to:

$ tmux set -g set-clipboard on

But that’s probably going to print a bunch of garbage to your screen every time you try to copy something, and not actually copy anything. You don’t want that.

You might be able to configure your terminal emulator to support these escape sequences. I only know that because man tmux will tell you how to configure xterm to do support them, if you look up the section on set-clipboard. So that’s neat.

But your terminal emulator might not support those escape sequences at all. And that’s okay! Things are only a tiny bit more complicated, if you’re using gnome-terminal or Terminal.app or something for some reason.

Instead of using copy-selection* commands, use copy-pipe* commands.

Here’s an example for macOS, if you’re using Terminal.app:

bind -T copy-mode-vi y send -X copy-pipe 'pbcopy'

pbcopy is a command that puts stdin on your clipboard – or “pasteboard,” as macOS used to call it. If you use Linux, you would do:

bind -T copy-mode-vi y send -X copy-pipe 'xsel --input --clipboard'

Or maybe:

bind -T copy-mode-vi y send -X copy-pipe 'xclip -i -selection clipboard'

Depending on what manner of Linux you have.

And then you can just ⌘V or ctrl-v like normal. This even works if you’re running tmux on a remote machine, as long as you ssh with X forwarding.1

So! Okay. You can do it. You can now copy text. And don’t forget that you can use copy-pipe-no-clear or copy-pipe-and-cancel, if you’re into that.

Now what?

Well, if you’re actually following along, you may have noticed that it’s annoying that there’s no way to stop selecting text, except to copy-and-cancel. If you press space again, it doesn’t stop selecting. It just starts a new selection in the new place. It’s annoying.

So let’s make space clear the current selection if there is one, and start a new selection if there isn’t. This is pretty easy to do:

bind -T copy-mode-vi Space  if -F "#{selection_present}" { send -X clear-selection } { send -X begin-selection }

You can sort of squint and see what’s happening, but it’s weird. The command we’re binding is if, which is short for if-shell. But what is -F? And why is there no else?

There’s no else because if-shell is just another command, like anything else, that takes other commands (as strings!) as arguments. tmux does not not actually have control-flow constructs. The braces are just a fancy way of quoting strings in tmux. You could also write:

bind -T copy-mode-vi Space if -F "#{selection_present}" "send -X clear-selection" "send -X begin-selection"

And indeed if you list-keys, you’ll see that tmux will report the keybindings in double quotes, even if we used brace notation in our config file.

Alright. But how does it work?

Well, normally if-shell "grep -q hello" would execute some arbitrary external command, given as a string.

But with -F, it doesn’t do that, and instead just checks if the provided string is empty or if the provided string is equal to the string "0". It’s weird and kind of janky, but what’s going on is this:

tmux performs variable expansion on the string. #{selection_preset} will either be replaced with the string "1" if there is a selection, or "" if there isn’t.

Then -F causes that string to be checked for “truthiness,” instead of treating it like an external command to run. If it’s truthy, the first “block” is executed. If it’s falsy, the second. (The empty string and "0" are both falsy. Everything else is truthy.)

This is the primitive conditional you have to work with when you’re customizing tmux. There’s actually a surprisingly thorough string-expansion language that lets you do, like, comparisons and logic and stuff all in the context of string expansion. It’s weird. But it works okay? I don’t know. I can’t really justify it.

Let’s check in with where we are now:

$ cat ~/.tmux.conf
...
bind -T copy-mode-vi Escape send -X cancel

bind -T copy-mode-vi Up     send -X cursor-up
bind -T copy-mode-vi Down   send -X cursor-down
bind -T copy-mode-vi Left   send -X cursor-left
bind -T copy-mode-vi Right  send -X cursor-right

bind -T copy-mode-vi Space  if -F "#{selection_present}" { send -X clear-selection } { send -X begin-selection }

bind -T copy-mode-vi y      send -X copy-selection-no-clear
bind -T copy-mode-vi Enter  send -X copy-selection-and-cancel

And that gets us to a reasonable, minimal starting point: we can scroll around; we can select text. We can copy text. And we can do it all from the safety of our keyboard.

but i like my mouse

Yes, okay, the mouse is often the best way to select a small amount of weirdly nestled text. And tmux has really good mouse support!

For starters, let’s add this to our ~/.tmux.conf:

set -g mouse on

That will cause tmux to steal mouse events. If you just do that, and reload your config, you might notice that your mouse no longer does anything. All this did was break the native selection – it doesn’t let us select text in tmux. The world is a worse place than it was before.

The way this works is that your terminal emulator is now – instead of natively handling mouse events – treating mouse events as “keys” that you’re “typing” into your terminal window. tmux sees those, and lets you handle them.

But your terminal emulator probably has a way to ignore tmux’s request to handle mouse events. On iTerm, if you hold down option, iTerm will handle the mouse events natively, instead of “typing” them into tmux.

Anyway, let’s get the mouse stuff working.

First things first: you may have noticed, over the course of following this walkthrough, that whenever you used your mouse to scroll up, something terrible happened. This is because tmux is a fullscreen application, like vim or whatever – you can “scroll up” past tmux, but you can’t scroll through the output of the shell running inside tmux, except by entering copy mode.

So that’s the first thing we’ll fix.

bind -n WheelUpPane copy-mode -e

The -n makes this a global keybinding, so we don’t need to use the prefix key before we scroll. copy-mode -e means “enter copy mode, but automatically exit if I ever scroll past the bottom.”

bind -T copy-mode-vi WheelUpPane   send -X -N 5 scroll-up
bind -T copy-mode-vi WheelDownPane send -X -N 5 scroll-down

And then we just bind the events to scroll in copy-mode. Pretty simple.

But of course we don’t just want to scroll. We want to select and copy, too.

bind -n MouseDrag1Pane copy-mode -M
bind -T copy-mode-vi MouseDrag1Pane    send -X begin-selection
bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-no-clear

Pretty easy! copy-mode -M means enter copy mode and start in a mouse selection. -T copy-mode MouseDrag1Pane lets us change our selection, so it works even if we are already in copy-mode. And the MouseDragEnd1Pane copies after we stop selecting, which is how most terminal emulators behave. (Remember to change that to copy-pipe-no-clear, if you aren’t using the escape sequence copy thing – and yeah, you can have it copy to a different clipboard on mouse select than on yank, if you’re using X).

are we done yet

Yep! That is a reasonable, working, minimalist tmux config. As long as you don’t ever intend to use multiple panes or splits.

But there are a few more things we can do to make the experience better.

First off, if we never use the tmux “tab” feature (tmux calls them “windows”), there’s not really a good reason to show the status bar – it’s basically just a clock. So let’s disable that:

set -g status off

And then let’s kick up our scrollback, so we’re less likely to lose old output:

set -g history-limit 50000

After that? It’s up to us to add in any extra functionality we want.

Or

We could just use the default copy-mode-vi.

Personally I created my first .tmux.conf by printing out all the defaults:

$ tmux -f /dev/null list-keys

And then deleting all the stuff I didn’t want, or re-binding the things I did. I’ve added to it over the years, and I’ve removed things that ended up not being useful.

Note that some of the default commands are much more complicated than the ones I showed here. Especially the mouse stuff: the default bindings are set up to work with multiple splits (“panes,” in tmux parlance) and will automatically focus the one you click on when you interact, and they’ll also pass mouse events through to the application running. If you use multiple panes, or run programs with mouse support, you should definitely copy those default commands.

onwards and upwards

So, where do you go from here?

Well, look at the defaults, and see what you like. If you’re curious about a command, look at man tmux. tmux has one of the best man pages that I’ve seen – all the docs are in a single page, so you can just search through that and find anything.

Lastly, although I’m presenting a barebones stripped-down “copy mode-only” tmux here, I would be remiss if I didn’t say: I love tmux’s splits. I love its windows, and its sessions. I use every bit of tmux, and even though I use iTerm, I still prefer tmux’s goofy emulated splits to iTerm’s fancy native ones (and I definitely prefer it to the weird tmux-integrated splits that it has, for some reason, which I have never really used).

I love tmux so much that I wrote a whole blog post about selecting command output in tmux. I have it all set up exactly how I like it, fit to my weird keyboard layout and everything.


  1. On Linux, anyway. I’m not sure what the Mac equivalent would be. Just use iTerm. ↩︎