tmux has 5 built-in layouts, mapped to <prefix> Alt+1..5 in this order:
even-horizontal — panes stacked side-by-side, equal width
┌───────┬───────┬───────┬───────┐
│ │ │ │ │
│ 1 │ 2 │ 3 │ 4 │
│ │ │ │ │
└───────┴───────┴───────┴───────┘
even-vertical — panes stacked top-to-bottom, equal height
┌───────────────────────────────┐
│ 1 │
├───────────────────────────────┤
│ 2 │
├───────────────────────────────┤
│ 3 │
├───────────────────────────────┤
│ 4 │
└───────────────────────────────┘
main-horizontal — one large pane on top, smaller ones across the bottom
┌───────────────────────────────┐
│ │
│ 1 │
│ │
├─────────┬──────────┬──────────┤
│ 2 │ 3 │ 4 │
└─────────┴──────────┴──────────┘
main-vertical — one large pane on the left, smaller ones stacked on the right
┌──────────────────┬────────────┐
│ │ 2 │
│ ├────────────┤
│ 1 │ 3 │
│ ├────────────┤
│ │ 4 │
└──────────────────┴────────────┘
tiled — roughly equal-sized grid
┌───────────────┬───────────────┐
│ 1 │ 2 │
├───────────────┼───────────────┤
│ 3 │ 4 │
└───────────────┴───────────────┘
You can cycle through these with <prefix> space
Note that tmux’s “horizontal” / “vertical” refers to the orientation of the divider line, not the arrangement of panes. So even-horizontal has a horizontal row of panes (with vertical dividers between them), and main-horizontal has the main pane separated from the rest by a horizontal divider.
Run tmux list-commands | grep select-layout or see man tmux under select-layout for the authoritative list.
1curl -LsSf https://astral.sh/uv/install.sh | sh # install uv
2uv tool install tmuxp # install tmuxp
3tmuxp load .tmuxp.yaml # load layout
tmuxp.tmux list-windows -F '#{window_layout}' command to get the string. You will get something like c2a1,203x50,0,0{...}.For example:
1# 3 vertical panes of equal size. last vertical pane is split once horizontally
2# ┌─────────┬─────────┬─────────┐
3# │ │ │ 3 │
4# │ 1 │ 2 ├─────────┤
5# │ │ │ 4 │
6# └─────────┴─────────┴─────────┘
7983f,305x53,0,0{112x53,0,0,1,106x53,113,0,2,85x53,220,0[85x26,220,0,3,85x26,220,27,4]}
Drop that string into a tmuxp.yaml as the layout key. Panes are assigned to the layout slots in order (left-to-right, top-to-bottom):
1session_name: work
2windows:
3 - window_name: main
4 layout: 983f,305x53,0,0{112x53,0,0,1,106x53,113,0,2,85x53,220,0[85x26,220,0,3,85x26,220,27,4]} # custom layout
5 panes:
6 - shell_command: nvim
7 - shell_command: npm run dev
8 - shell_command: htop
9 - shell_command: tail -f log
Here is the default example
1session_name: 4-pane-split
2windows:
3 - window_name: dev window
4 layout: tiled # built-in tmux layout
5 shell_command_before:
6 - cd ~/ # run as a first command in all panes
7 panes:
8 - shell_command: # pane no. 1
9 - cd /var/log # run multiple commands in this pane
10 - ls -al | grep \.log
11 - echo second pane # pane no. 2
12 - echo third pane # pane no. 3
13 - echo fourth pane # pane no. 4
The examples above are all running a command in each pane. If you don’t want to run any commands, then add an empty item with -. The total amount of
panes defined with - need to match with the amount of panes defined in the layout string.
You can define multiple commands to run in a pane. e.g.
1- shell-command:
2 - export NODE_ENV=development
3 - nvm use lts
4 - npm run dev
You can decide which pane gets focus with focus: true. e.g.
1- shell_command: claude
2 focus: true
To run a command in all panes:
1shell_command_before:
2 - cd ~/ # run as a first command in all panes
Other values you can define: session_name, start_directory
If you live in tmux, you’ve probably wished tmux would just know which layout to start. tmuxp already handles the layout part — YAML configs that describe your windows and panes. What’s missing is a single command that picks the right config for wherever you are.
Here’s a small bash function, t, that does exactly that. It grew out of a few rounds of refinement, and each step is worth understanding on its own.
The simplest version: if there’s a tmuxp.yaml in the current directory, load it. Otherwise, fall back to plain tmux.
1t() {
2 if [[ -f tmuxp.yaml ]]; then
3 tmuxp load tmuxp.yaml
4 else
5 tmux
6 fi
7}
Drop this in your ~/.bashrc or ~/.zshrc, source it, and any project folder with a tmuxp.yaml now has a one-keystroke launcher.
Most projects don’t have a custom layout — but you probably still want a consistent starting shape (say: an editor pane, a shell pane, a log tailer). Save that once in ~/.tmuxp/default.yaml and have t fall back to it:
1t() {
2 local default="$HOME/.tmuxp/default.yaml"
3 if [[ -f tmuxp.yaml ]]; then
4 tmuxp load tmuxp.yaml
5 elif [[ -f $default ]]; then
6 tmuxp load "$default"
7 else
8 tmux
9 fi
10}
Now t has three modes: project layout, personal default, bare tmux.
A minimal ~/.tmuxp/default.yaml might look like:
1windows:
2 - window_name: dev
3 focus: true
4 panes:
5 - shell_command: vim
6 - shell_command: htop
7 focus: true
focus: true is how you tell tmuxp which pane (and window) to land on when the session attaches.
By default, tmuxp uses whatever session_name is in the YAML. That means every project you open via the default config ends up with the same session name — annoying if you keep a few open at once.
The fix: pass -s <name> to tmuxp load (it overrides the YAML’s session_name), and use ${PWD##*/} to grab just the current folder name.
1t() {
2 local default="$HOME/.tmuxp/default.yaml"
3 local name="${PWD##*/}"
4 name="${name//[.:]/_}" # tmux forbids . and : in names
5
6 if [[ -f tmuxp.yaml ]]; then
7 tmuxp load -s "$name" tmuxp.yaml
8 elif [[ -f $default ]]; then
9 tmuxp load -s "$name" "$default"
10 else
11 tmux new -s "$name"
12 fi
13}
Now each folder gets its own session, named after itself.
session_name in the YAMLOverriding is fine for the default layout, but if a project’s tmuxp.yaml deliberately sets session_name, we should honor it. Only supply -s when the file doesn’t already name the session:
1t() {
2 local default="$HOME/.tmuxp/default.yaml"
3 local name="${PWD##*/}"
4 name="${name//[.:]/_}"
5
6 _tmuxp_load() {
7 local file=$1
8 if grep -qE '^\s*session_name\s*:' "$file"; then
9 tmuxp load "$file"
10 else
11 tmuxp load -s "$name" "$file"
12 fi
13 }
14
15 if [[ -f tmuxp.yaml ]]; then
16 _tmuxp_load tmuxp.yaml
17 elif [[ -f $default ]]; then
18 _tmuxp_load "$default"
19 else
20 tmux new -s "$name"
21 fi
22}
That’s the full function. A quick grep for a top-level session_name: decides whether to pass -s or not.
t works in any directory.~/.tmuxp/ and forget about it.tmuxp.yaml in the repo root.tmuxp auto-discovers any YAML in ~/.tmuxp/, so alternates are a tmuxp load <name> away. ~/.tmuxp/work.yaml → tmuxp load work. Pair that with t for the common case and you’ve got the best of both worlds.