This document provides production-ready specifications for implementing a declarative, Nix flake-based tmux configuration for the Kalilix project. The implementation eliminates imperative plugin management (TPM), achieves hermetic reproducibility through home-manager, and delivers a 90% solution for modern terminal-based development workflows targeting macOS, Linux, and WSL environments.
Core recommendation: Use home-manager's programs.tmux module with tmuxp for session management, vim-tmux-navigator for neovim integration, and Catppuccin Macchiato theme with samoshkin's F12 nested session pattern for remote work. This combination provides battle-tested reliability, excellent Nix integration, and minimal cognitive overhead.
Why this matters: Developers waste 5-10 minutes daily on tmux configuration inconsistencies, SSH clipboard issues, and context switching between local/remote sessions. A declarative, reproducible tmux setup eliminates this friction while maintaining flexibility for team collaboration through git-committed configuration.
Timeline: Implementation can proceed in three phases over 2-3 weeks: Phase 1 (core architecture), Phase 2 (integration and theming), Phase 3 (advanced features and optimization).
The Kalilix tmux implementation uses Nix flakes exclusively for package management, replacing traditional imperative plugin managers like TPM with home-manager's native plugin system. This architectural choice provides hermetic builds, reproducible environments across machines, and git-committable configurations that teams can share without manual setup steps.
Home-manager programs.tmux module serves as the foundation, offering 25 configuration options including baseIndex, escapeTime, historyLimit, keyMode, mouse support, and prefix key customization. The module generates ~/.config/tmux/tmux.conf automatically from declarative specifications, ensuring consistency between declared state and runtime behavior.
Plugin management pattern: Instead of TPM's runtime git cloning, use pkgs.tmuxPlugins with the mkTmuxPlugin function for custom plugins not in nixpkgs. The nixpkgs repository includes 57+ pre-packaged plugins (vim-tmux-navigator, catppuccin, sensible, yank, resurrect) that install to /nix/store with proper dependency resolution. Each plugin supports an extraConfig attribute for plugin-specific settings, maintaining declarative principles throughout.
Module structure follows existing Kalilix patterns: Place tmux configuration in modules/devshells/tmux.nix or modules/programs/tmux/default.nix with supporting files split into core.conf, keybindings.conf, nested-session.conf for maintainability. This structure mirrors the neovim implementation pattern already established in Kalilix.
Export tmux configurations as flake outputs for consumption by devShells and other projects. The recommended pattern creates a library function mkTmuxConfig that accepts parameters (shell, plugins, extra-config) and returns a wrapped tmux executable. This enables per-project customization while maintaining shared base configuration.
{
outputs = { self, nixpkgs, ... }: {
lib.mkTmuxConfig = { pkgs, shell, plugins, extra-config }:
# Implementation creates wrapped tmux with custom config
packages = forAllSystems (system: {
default = mkTmuxConfig { inherit pkgs; };
tmux = mkTmuxConfig { inherit pkgs; };
});
overlays.default = final: prev: {
kalilixTmux = mkTmuxConfig { pkgs = final; /* ... */ };
};
};
}Cross-platform considerations: Use lib.optionalString pkgs.stdenv.isDarwin for macOS-specific configuration (pbcopy integration, reattach-to-user-namespace) and lib.optionalString pkgs.stdenv.isLinux for Linux variants (xsel/xclip, Wayland wl-clipboard). The flake should support x86_64-linux, aarch64-linux, x86_64-darwin, and aarch64-darwin systems.
Research shows vim-tmux-navigator remains the gold standard for seamless tmux/neovim pane navigation despite newer alternatives like tmux.nvim and nvim-tmux-navigation. With 6,400+ GitHub stars, 12+ years of development, and compatibility with both Vim and Neovim, vim-tmux-navigator provides the most mature implementation of unified navigation.
Technical implementation: The plugin uses process detection via ps command to identify vim/neovim processes in tmux panes, forwarding Ctrl-hjkl keypresses to vim when detected and executing tmux pane selection otherwise. This creates a single mental model: Ctrl-h/j/k/l navigates left/down/up/right regardless of whether you're moving between vim splits or tmux panes.
Why not tmux.nvim: While tmux.nvim offers additional features (pane resizing with Alt-hjkl, clipboard sync, window navigation), the maintainer no longer uses tmux and development has stalled. The plugin's clipboard sync can cause input lag on slower machines. For Kalilix's 90% use case philosophy, vim-tmux-navigator's focused approach (navigation only) provides better reliability.
Why not nvim-tmux-navigation: This Lua rewrite targets Neovim 0.7+ exclusively, sacrificing Vim compatibility. While modern and actively maintained, its smaller community (400 stars) means less battle-testing and fewer resolved edge cases.
Set tmux prefix to Ctrl-a instead of default Ctrl-b for ergonomic superiority: Ctrl-a requires less finger stretching, aligns with GNU Screen muscle memory for users migrating from screen, and enjoys widespread adoption in the tmux community. The configuration should maintain Ctrl-b as a secondary prefix for compatibility.
programs.tmux = {
prefix = "C-a";
extraConfig = ''
# Dual prefix system for compatibility
set -g prefix2 C-b
bind C-a send-prefix
bind C-b send-prefix -2
'';
};Base index 1: Windows and panes should start at index 1, matching keyboard number row layout. Default index 0 creates cognitive dissonance between visual position and keyboard access.
Intuitive splits: Replace cryptic default bindings (% for horizontal, " for vertical) with visual mnemonic bindings. Use | (vertical line) for vertical splits and - (horizontal line) for horizontal splits. Crucially, open splits in current working directory with #{pane_current_path}.
extraConfig = ''
# Start numbering at 1
set -g base-index 1
setw -g pane-base-index 1
# Visual split keybindings
unbind '"'
unbind %
bind '|' split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
# Also support \ as easier to type than |
bind '\' split-window -h -c "#{pane_current_path}"
'';Additional human-friendly settings: Enable mouse support for beginners, set vi-mode keys for copy mode, enable automatic window renaming, set aggressive-resize for multi-client scenarios, and create a prefix+r binding for config reload with visual confirmation message.
Beyond navigation, implement vim-style pane resizing with repeatable bindings (bind -r), window cycling with prefix+Tab for last-window and Alt-H/L for previous/next without prefix, and copy mode enhancements with v for begin-selection, C-v for rectangle-toggle, and y for copy-selection-and-cancel.
OSC 52 escape sequences represent the modern solution for tmux clipboard integration, working seamlessly over SSH without X11 forwarding, across nested tmux sessions, and without external dependencies. This ANSI standard allows terminal applications to write to the system clipboard via escape codes that terminals intercept and handle.
Implementation in tmux 3.2+:
extraConfig = ''
# Enable OSC 52 clipboard support
set -s set-clipboard on
# Enable passthrough for nested sessions (tmux 3.3a+)
set -g allow-passthrough on
# Configure terminal capabilities
set -as terminal-features ',xterm*:clipboard'
# For older tmux versions, use terminal-overrides
set -as terminal-overrides ',xterm*:Ms=\\E]52;%p1%s;%p2%s\\007'
'';Terminal emulator requirements: Ghostty, iTerm2, Kitty, Alacritty, and WezTerm all support OSC 52. iTerm2 requires enabling "Applications in terminal may access clipboard" in Preferences → General. Kitty needs clipboard_control write-primary write-clipboard no-append in kitty.conf. GNOME Terminal and XFCE Terminal do NOT support OSC 52 and require fallback to external tools.
For local workflows where OSC 52 isn't sufficient, integrate pbcopy/pbpaste on macOS and xsel/xclip/wl-copy on Linux as secondary clipboard mechanisms. The configuration should detect the platform and available tools dynamically.
macOS configuration: Some macOS versions require reattach-to-user-namespace to access pbcopy from tmux. Install via nixpkgs and wrap the clipboard command.
Linux configuration: Support both X11 (xclip/xsel) and Wayland (wl-clipboard) display servers. Xclip requires redirecting stdout to /dev/null to prevent tmux hangs. Copy to both PRIMARY and CLIPBOARD selections for maximum compatibility.
Cross-platform Nix implementation:
{ pkgs, lib, ... }: {
home.packages = with pkgs; lib.optionals stdenv.isLinux [
xsel
wl-clipboard
] ++ lib.optionals stdenv.isDarwin [
reattach-to-user-namespace
];
programs.tmux.extraConfig = ''
# Vi-mode bindings
bind-key -T copy-mode-vi 'v' send-keys -X begin-selection
bind-key -T copy-mode-vi 'C-v' send-keys -X rectangle-toggle \; send -X begin-selection
bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel
# Platform-specific clipboard integration
if-shell 'test "$(uname)" = "Darwin"' \
"bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'reattach-to-user-namespace pbcopy'"
if-shell 'test "$(uname)" = "Linux" -a -n "$DISPLAY"' \
"bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'xsel -i --clipboard > /dev/null'"
if-shell 'echo $XDG_SESSION_TYPE | grep -q wayland' \
"bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'wl-copy'"
'';
}Neovim 10.0+ includes built-in OSC 52 support via vim.ui.clipboard.osc52. Configure neovim to use OSC 52 as the primary clipboard provider when running inside tmux, ensuring yanks in neovim automatically reach the system clipboard and pastes work bidirectionally.
Neovim configuration (init.lua):
-- Use OSC 52 for clipboard in tmux
if os.getenv("TMUX") then
vim.g.clipboard = {
name = 'OSC 52',
copy = {
['+'] = require('vim.ui.clipboard.osc52').copy('+'),
['*'] = require('vim.ui.clipboard.osc52').copy('*'),
},
paste = {
['+'] = require('vim.ui.clipboard.osc52').paste('+'),
['*'] = require('vim.ui.clipboard.osc52').paste('*'),
},
}
end
vim.opt.clipboard:append('unnamedplus')Alternative for pre-10.0 neovim: Use the nvim-osc52 plugin with tmux_passthrough = true configuration. The plugin provides more features including auto-copy-on-yank and maximum length limits.
The ideal configuration provides four interconnected clipboard zones: tmux panes (copy mode with vi bindings), neovim buffers (yank/put operations), separate neovim instances (via tmux buffer sharing), and system clipboard (via OSC 52 + platform tools). Users should experience zero friction: copy in tmux with prefix+[ v y, paste in neovim with p, copy in neovim with yy, paste in tmux with prefix+], and copy/paste from external applications work seamlessly.
Catppuccin theme has become the de facto standard for modern terminal theming, with official support across 200+ applications. The tmux implementation integrates seamlessly with Nix through pkgs.tmuxPlugins.catppuccin, providing four flavors (latte, frappe, macchiato, mocha) with extensive customization options.
Macchiato flavor offers the best balance: darker than frappe but lighter than mocha, with excellent contrast for prolonged viewing. The theme supports rounded window separators via Nerd Font icons, customizable status modules, and automatic color coordination with active/inactive pane borders.
Implementation pattern:
programs.tmux.plugins = with pkgs.tmuxPlugins; [
{
plugin = catppuccin;
extraConfig = ''
set -g @catppuccin_flavour 'macchiato'
set -g @catppuccin_window_status_style "rounded"
# Rounded separators using Nerd Font icons
set -g @catppuccin_window_left_separator ""
set -g @catppuccin_window_right_separator " "
set -g @catppuccin_window_middle_separator " █"
set -g @catppuccin_window_number_position "right"
# Window text configuration
set -g @catppuccin_window_default_fill "number"
set -g @catppuccin_window_default_text "#W"
set -g @catppuccin_window_current_fill "number"
set -g @catppuccin_window_current_text "#W"
# Status modules (minimal configuration)
set -g @catppuccin_status_modules_right "directory session"
set -g @catppuccin_status_left_separator " "
set -g @catppuccin_status_right_separator ""
set -g @catppuccin_status_fill "icon"
set -g @catppuccin_status_connect_separator "no"
'';
}
];Rounded window style options: The theme supports basic, rounded, slanted, custom, and none styles. Rounded provides the most modern appearance, using powerline-style icons ( ) for visual separation. Custom separators can be defined but require careful Nerd Font glyph selection.
Nerd Fonts package in nixpkgs provides patched fonts with 3,600+ icons for terminal interfaces. Fira Code Nerd Font combines Fira Code's excellent ligatures with Nerd Font's icon set, serving as the ideal choice for tmux status bars and catppuccin separators.
Installation via Nix flakes:
# For NixOS 25.05+ (new structure)
fonts.packages = with pkgs; [
nerd-fonts.fira-code
];
# For home-manager
home.packages = with pkgs; [
nerd-fonts.fira-code
];
# Configure terminal emulator
programs.ghostty.settings = {
font-family = "FiraCode Nerd Font";
};Font availability assertion: Add build-time checks to verify font installation, preventing runtime issues where separators render as boxes or question marks:
assertions = [
{
assertion = builtins.elem pkgs.nerd-fonts.fira-code config.fonts.packages;
message = "FiraCode Nerd Font must be installed for tmux theme";
}
];ASCII fallbacks for SSH contexts: When SSH'ing to remote servers without Nerd Fonts, icons render incorrectly. Implement detection and fallback separators:
extraConfig = ''
# Detect SSH and use simple ASCII separators
if-shell 'test -n "$SSH_CLIENT"' {
set -g @catppuccin_window_left_separator "["
set -g @catppuccin_window_right_separator "]"
}
'';24-bit true color support requires correct configuration at three layers: terminal emulator, tmux, and neovim. Misconfiguration at any layer results in color degradation, banding, or incorrect theme rendering.
Terminal emulator layer: Set TERM=xterm-256color in terminal settings (not shell configuration). Most modern terminals (Ghostty, Alacritty, Kitty, iTerm2) default to this correctly.
Tmux layer: Use tmux-256color as default-terminal and add RGB capability via terminal-overrides:
programs.tmux = {
terminal = "tmux-256color";
extraConfig = ''
# True color support
set -g default-terminal "tmux-256color"
set -ag terminal-overrides ",xterm-256color:RGB"
# Alternative using terminal-features (tmux 3.2+)
set -as terminal-features ",xterm*:RGB"
# Specific terminal emulator overrides
set -ag terminal-overrides ",alacritty:RGB"
set -ag terminal-overrides ",kitty:RGB"
set -ag terminal-overrides ",ghostty:RGB"
'';
};Neovim layer: Enable termguicolors to utilize true color capabilities:
vim.opt.termguicolors = trueVerification: Test color support by running tmux info | grep RGB (should show RGB: [present]) and color gradient test scripts from invisible-island.net.
Research across top tmux configurations reveals two successful approaches: ultra-minimal (session name + time only) and information-rich (CPU, battery, network, date, time). The Kalilix implementation should default to minimal with optional widgets.
Minimal configuration (via Catppuccin):
extraConfig = ''
# Status bar positioning
set -g status-position top
set -g status-interval 5
# Minimal modules
set -g @catppuccin_status_modules_right "session directory"
# Custom right side
set -ag status-right " %H:%M %d-%b"
'';Information-rich alternative (for developers wanting more data):
plugins = with pkgs.tmuxPlugins; [
catppuccin
cpu
battery
];
extraConfig = ''
set -g @catppuccin_status_modules_right "cpu battery session uptime"
'';Avoid overload: Status bars consuming more than 50% of screen width or updating more frequently than every 5 seconds create visual noise and performance degradation. Stick to 3-5 widgets maximum.
Ghostty terminal emulator has excellent tmux compatibility with some specific considerations. As a modern GPU-accelerated terminal, Ghostty supports true color, Kitty Graphics Protocol, and custom keybindings natively.
Basic Ghostty + tmux configuration:
programs.ghostty = {
enable = true;
settings = {
font-family = "FiraCode Nerd Font";
theme = "catppuccin-macchiato";
term = "xterm-256color";
};
};
programs.tmux = {
terminal = "tmux-256color";
extraConfig = ''
set -ag terminal-overrides ",xterm*:RGB"
'';
};SSH considerations: When SSH'ing from Ghostty to remote servers, the remote may not recognize the xterm-ghostty TERM value. Either set term = "xterm-256color" in Ghostty config for broader compatibility, or copy terminfo to remote servers using infocmp xterm-ghostty > xterm-ghostty.terminfo and tic -x xterm-ghostty.terminfo on remote.
Auto-start tmux pattern: Create a shell script that attaches to existing session or creates new one, then set Ghostty's command setting to execute this script on launch.
After comprehensive comparison, tmuxp emerges as the clear winner for declarative session layouts in Nix environments. The Python-based architecture avoids Ruby version manager conflicts that plague tmuxinator, native support for both YAML and JSON formats provides flexibility, built-in tmuxp freeze command captures ad-hoc layouts for later reproduction, and the underlying libtmux library enables programmatic tmux control.
Feature comparison highlights:
- Session freezing: tmuxp uniquely offers
tmuxp freeze session-name > config.yamlto capture existing sessions - Config import: tmuxp can import teamocil and tmuxinator configs, easing migration
- Format conversion:
tmuxp convert config.yamltranslates between YAML and JSON - ORM API: libtmux provides object-oriented tmux interaction for advanced scripting
- Nix integration: Both have
programs.tmux.tmuxp.enableandprograms.tmux.tmuxinator.enableoptions
Why not tmuxinator: Requires Ruby runtime, creating dependency on rbenv/rvm/chruby version managers. YAML-only configuration limits flexibility. No session freezing or import capabilities. Generally less suited for Python-heavy workflows common in modern development.
The ideal pattern places .tmuxp.yaml files in project root directories, committed to version control alongside code. This enables zero-setup development environments: clone repository, run tmuxp load ., and immediately have the correct windows, panes, and shell states.
Example Kalilix project configuration:
# ~/projects/kalilix/.tmuxp.yaml
session_name: kalilix
start_directory: ./
# Bootstrap script runs before creating windows
before_script: |
#!/usr/bin/env bash
set -e
# Ensure Nix environment is ready
nix develop --command true
environment:
RUST_BACKTRACE: "1"
CARGO_INCREMENTAL: "1"
windows:
# Main editing window
- window_name: editor
layout: main-horizontal
options:
main-pane-height: 70
focus: true
panes:
- focus: true
shell_command:
- nix develop
- nvim
- shell_command:
- nix develop
- clear
# Build and test window
- window_name: build
layout: even-vertical
panes:
- shell_command:
- nix develop
- cargo watch -x check -x test
- shell_command:
- nix develop
- clear
# Shell for ad-hoc commands
- window_name: shell
panes:
- shell_command:
- nix develop
- clearUsage workflow:
cd ~/projects/kalilix
tmuxp load . # Loads .tmuxp.yaml from current directory
# Or from anywhere
tmuxp load ~/projects/kalilix/.tmuxp.yaml
# With Nix integration
nix develop --command tmuxp load .The nested session problem: When running tmux locally and SSH'ing to a remote server that also uses tmux, all keybindings get captured by the outer session. The inner session becomes inaccessible, creating a frustrating UX with multiple overlapping status bars and no way to control the remote tmux.
samoshkin's F12 toggle solution is the gold standard implementation, adopted widely across the tmux community. Pressing F12 toggles the outer tmux session into "OFF" mode: prefix becomes None, key-table switches to "off", status bar changes color to visually indicate disabled state, and all keybindings pass through to the inner session. Pressing F12 again restores normal operation.
Complete implementation:
# Color scheme variables
color_status_text="colour245"
color_window_off_status_bg="colour238"
color_window_off_status_current_bg="colour254"
color_light="white"
color_dark="colour232"
# F12 to toggle OFF
bind -T root F12 \
set prefix None \;\
set key-table off \;\
set status-style "fg=$color_status_text,bg=$color_window_off_status_bg" \;\
set window-status-current-format "#[fg=$color_window_off_status_bg,bg=$color_window_off_status_current_bg] #I:#W #" \;\
set window-status-current-style "fg=$color_dark,bold,bg=$color_window_off_status_current_bg" \;\
if -F '#{pane_in_mode}' 'send-keys -X cancel' \;\
refresh-client -S
# F12 in OFF mode to restore
bind -T off F12 \
set -u prefix \;\
set -u key-table \;\
set -u status-style \;\
set -u window-status-current-style \;\
set -u window-status-current-format \;\
refresh-client -S
# Visual indicator in status bar
wg_is_keys_off="#[fg=$color_light,bg=colour196]#{?#{==:#{key-table},off},[OFF],}#[default]"
set -g status-right "$wg_is_keys_off | %Y-%m-%d %H:%M"How it works: The binding switches to an alternate key table named "off" which has no bindings except F12, effectively disabling all tmux hotkeys. The status bar color change (grey background) provides clear visual feedback. All keypresses pass through to the underlying shell/application, reaching the inner tmux session.
Beyond F12 toggle, tmux should detect SSH sessions and apply different defaults automatically. Remote sessions benefit from status bar at bottom (so nested local+remote status bars don't stack at top), simplified status content (conserve bandwidth and width), and environment variable refresh (keep SSH_AUTH_SOCK valid).
SSH detection pattern:
# Create separate remote configuration file
home.file.".tmux.remote.conf".text = ''
set -g status-position bottom
set -g status-left-length 20
set -g status-right-length 50
set -g status-right "#{hostname} | %H:%M"
'';
programs.tmux.extraConfig = ''
# Load remote config if SSH session detected
if-shell 'test -n "$SSH_CLIENT"' \
'source-file ~/.tmux.remote.conf'
# Alternative: check SSH_TTY
if-shell '[ -n "$SSH_TTY" ]' \
'set -g status-position bottom'
'';The stale environment problem: When you SSH with agent forwarding, attach to a long-running tmux session, then SSH disconnect and reconnect later, $SSH_AUTH_SOCK points to a non-existent socket. Git operations fail, SSH commands fail, and you must detach/reattach tmux or restart panes to fix it.
Symlink solution (most robust):
# ~/.ssh/rc (runs on every SSH connection)
#!/bin/bash
if test "$SSH_AUTH_SOCK" ; then
ln -sf "$SSH_AUTH_SOCK" ~/.ssh/ssh_auth_sock
fi# tmux configuration
programs.tmux.extraConfig = ''
# Remove SSH_AUTH_SOCK from update-environment
set -g update-environment "DISPLAY SSH_ASKPASS SSH_AGENT_PID \
SSH_CONNECTION WINDOWID XAUTHORITY"
# Use symlink for SSH authentication
setenv -g SSH_AUTH_SOCK $HOME/.ssh/ssh_auth_sock
'';Shell hook solution (for existing panes):
# ~/.zshrc or ~/.bashrc
if [ -n "$TMUX" ]; then
function refresh_tmux_env() {
eval $(tmux show-env -s 2>/dev/null)
}
# Refresh before each prompt
autoload -Uz add-zsh-hook
add-zsh-hook precmd refresh_tmux_env
fiManual refresh binding:
bind '$' run "~/bin/tmux-renew-env.sh"Where the script sources the latest tmux environment into all panes.
tmux-resurrect and tmux-continuum promise automatic session persistence across reboots, but introduce significant reliability issues that conflict with declarative configuration philosophy:
Known problems:
- Race conditions during shutdown: Auto-save during reboot causes corruption, leaving tmux unable to start
- Empty session creation: Restore creates unwanted session "0" that must be manually killed
- Status bar conflicts: Continuum modifies status-right, breaking theme plugins loaded afterward
- Limited process restoration: Can restore vim (with strategy), some shells, but NOT SSH connections, running servers, compiled programs, or background jobs
- Platform limitations: Process detection broken on Cygwin and some BSD variants
Declarative alternative: Use tmuxp YAML configs that rebuild sessions idempotently. Unlike resurrect's binary state files, YAML configs are git-committable, debuggable, and shareable. Running tmuxp load project.yaml produces identical results every time, implementing true infrastructure-as-code for tmux sessions.
When resurrect/continuum might be acceptable: Simple stable layouts, no theme plugins, understanding of limitations, and placing resurrect/continuum last in plugin list (after all theme plugins). Set reasonable expectations: it saves window/pane layout and some processes, not full application state.
This configuration provides the 90% solution: Ctrl-a prefix, vim navigation, Catppuccin theme, clipboard integration, and human-friendly defaults. Total configuration under 100 lines.
{ config, pkgs, lib, ... }:
{
programs.tmux = {
enable = true;
# Core settings
terminal = "tmux-256color";
historyLimit = 50000;
baseIndex = 1;
keyMode = "vi";
mouse = true;
escapeTime = 0;
prefix = "C-a";
# Essential plugins
plugins = with pkgs.tmuxPlugins; [
sensible
yank
vim-tmux-navigator
{
plugin = catppuccin;
extraConfig = ''
set -g @catppuccin_flavour 'macchiato'
set -g @catppuccin_window_status_style "rounded"
set -g @catppuccin_status_modules_right "directory session"
'';
}
];
extraConfig = ''
# True color support
set -ga terminal-overrides ",xterm-256color:RGB"
# Human-friendly defaults
setw -g pane-base-index 1
set -g renumber-windows on
# Intuitive splits
unbind '"'
unbind %
bind '|' split-window -h -c "#{pane_current_path}"
bind '\' split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
# Reload config
bind r source-file ~/.config/tmux/tmux.conf \; display "Config reloaded!"
# Vi-mode copy bindings
bind-key -T copy-mode-vi 'v' send-keys -X begin-selection
bind-key -T copy-mode-vi 'C-v' send-keys -X rectangle-toggle
bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel
# OSC 52 clipboard support
set -s set-clipboard on
set -g allow-passthrough on
# Platform-specific clipboard fallback
if-shell 'test "$(uname)" = "Darwin"' \
"bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'pbcopy'"
if-shell 'test "$(uname)" = "Linux"' \
"bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'xsel -i --clipboard'"
'';
};
# Platform-specific packages
home.packages = with pkgs; lib.optionals stdenv.isLinux [
xsel
];
}For production deployments requiring nested sessions, SSH awareness, and team collaboration features:
# modules/tmux/default.nix
{ config, pkgs, lib, ... }:
{
imports = [
./core.nix
./theme.nix
./nested-session.nix
./ssh-aware.nix
];
programs.tmux = {
enable = true;
terminal = "tmux-256color";
historyLimit = 50000;
baseIndex = 1;
keyMode = "vi";
mouse = true;
escapeTime = 0;
prefix = "C-a";
tmuxp.enable = true;
plugins = with pkgs.tmuxPlugins; [
sensible
yank
vim-tmux-navigator
{
plugin = catppuccin;
extraConfig = builtins.readFile ./catppuccin-config.conf;
}
];
extraConfig = builtins.concatStringsSep "\n" [
(builtins.readFile ./core.conf)
(builtins.readFile ./keybindings.conf)
(builtins.readFile ./nested-session.conf)
(builtins.readFile ./clipboard.conf)
''
# SSH awareness
if-shell 'test -n "$SSH_CLIENT"' \
'source-file ~/.config/tmux/remote.conf'
# User overrides
if-shell '[ -f ~/.config/tmux/local.conf ]' \
'source-file ~/.config/tmux/local.conf'
''
];
};
# Support files
home.file = {
".config/tmux/remote.conf".text = ''
set -g status-position bottom
set -g status-right "#{hostname} | %H:%M"
'';
".config/tmux/local.conf".text = ''
# User-specific overrides (mutable)
'';
".config/tmuxp/kalilix.yaml".source = ./tmuxp/kalilix.yaml;
};
home.packages = with pkgs; [
tmuxp
] ++ lib.optionals stdenv.isLinux [
xsel
wl-clipboard
] ++ lib.optionals stdenv.isDarwin [
reattach-to-user-namespace
];
}For per-project tmux configurations that activate with nix develop:
# flake.nix
{
outputs = { self, nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
name = "kalilix-dev";
buildInputs = with pkgs; [
tmux
tmuxp
# other dev tools
];
shellHook =
let
tmuxConf = pkgs.writeText "kalilix-tmux.conf" ''
# Project-specific tmux settings
set -g status-right '#[fg=green]⎈ kalilix'
# Custom keybindings for project
bind K send-keys "cargo check" Enter
bind T send-keys "cargo test" Enter
'';
in
''
export TMUX_CONFIG_PROJECT="${tmuxConf}"
alias ktmux="tmux -f $TMUX_CONFIG_PROJECT"
# Auto-start tmux if not already in session
if [ -z "$TMUX" ] && [ -f .tmuxp.yaml ]; then
echo "Starting Kalilix dev tmux session..."
tmuxp load .
fi
'';
};
};
}The single most impactful performance optimization: set escape-time to 0. By default, tmux waits 500 milliseconds after receiving an ESC character to determine if it's a standalone keypress or the start of an escape sequence (arrow keys, function keys). This creates noticeable lag when switching from insert to normal mode in vim/neovim.
Configuration:
programs.tmux.escapeTime = 0;Considerations: Setting to 0 can cause issues with Alt-key shortcuts if your terminal sends Alt as ESC+key, and function keys on some terminals. For high-latency SSH connections, use 10-20 milliseconds instead. Local sessions should use 0-5 milliseconds for optimal responsiveness.
Balance history size with performance: Default 2,000 lines is insufficient for modern development workflows, but history limits exceeding 1 million lines cause noticeable slowdown during scrollback searches and window switching.
Recommended settings:
- Standard users: 10,000-20,000 lines
- Heavy users: 50,000 lines
- Never exceed: 100,000 lines
programs.tmux.historyLimit = 50000;Reduce status-interval to minimize redraws: Default 15 seconds is fine for static status bars, but information-rich status bars updating every second create unnecessary CPU usage and visual flicker.
extraConfig = ''
set -g status-interval 5 # Update every 5 seconds
'';For status bars with system statistics (CPU, memory, battery), 5-10 seconds provides good balance. For minimal status bars (session name + time only), 15-60 seconds is sufficient.
extraConfig = ''
# Enable focus events for vim/neovim
set -g focus-events on
# Aggressive resize (smart window sizing)
setw -g aggressive-resize on
# Disable visual activity monitoring
setw -g monitor-activity off
set -g visual-activity off
# Disable visual bell
set -g visual-bell off
'';Don't: Use complex status lines with shell command substitutions that run frequently, set history-limit above 1 million, monitor activity on many windows simultaneously, run heavy compilation processes in visible panes (use separate windows), or create deeply nested tmux sessions (3+ levels).
Do: Keep status bars simple, use separate windows for background tasks, limit visible pane count to what you actually need, and leverage tmux-resurrect for session persistence instead of never-ending sessions that accumulate cruft.
Objective: Establish working declarative tmux configuration with basic functionality.
Tasks:
- Create
modules/devshells/tmux.nixormodules/programs/tmux/default.nixfollowing Kalilix patterns - Configure home-manager programs.tmux with basic settings: terminal, historyLimit, baseIndex, keyMode, mouse, escapeTime, prefix
- Implement essential plugins: sensible, vim-tmux-navigator
- Set up human-friendly keybindings: Ctrl-a prefix, | and - splits, vi-mode copy bindings
- Configure OSC 52 clipboard support with set-clipboard on
- Test on primary development machine (macOS or Linux)
Success criteria: Can start tmux, split panes, navigate with Ctrl-hjkl, copy/paste with vi-mode, and reload configuration without errors.
Estimated time: 3-5 hours (configuration), 2-3 hours (testing and refinement)
Objective: Add Catppuccin theme, enhance clipboard integration, ensure cross-platform compatibility.
Tasks:
- Integrate Catppuccin plugin with Macchiato flavor and rounded window style
- Install Fira Code Nerd Font via Nix and verify rendering
- Implement platform-specific clipboard fallbacks (xsel for Linux, pbcopy for macOS)
- Configure neovim clipboard synchronization via OSC 52
- Test true color support: verify tmux-256color, terminal-overrides for RGB
- Create tmuxp YAML configuration for Kalilix project
- Enable tmuxp in home-manager and test
tmuxp load .workflow - Test on secondary platform (if primary is macOS, test Linux; vice versa)
Success criteria: Theme renders correctly with rounded separators, clipboard works bidirectionally between tmux/neovim/system, tmuxp loads project sessions successfully, works on both macOS and Linux.
Estimated time: 4-6 hours (configuration and integration), 3-4 hours (cross-platform testing)
Objective: Implement nested session support, SSH awareness, finalize performance tuning.
Tasks:
- Implement F12 nested session toggle with visual indicators
- Create SSH-aware configuration with remote.conf for automatic switching
- Set up SSH agent socket renewal (symlink pattern in ~/.ssh/rc)
- Configure environment variable refresh for long-running sessions
- Performance audit: verify escape-time 0, optimize history-limit, review status-interval
- Create documentation for team: README with setup instructions, troubleshooting guide
- Add example .tmuxp.yaml files for common project types
- Test nested session scenarios: local tmux → SSH → remote tmux
- Verify Ghostty compatibility specifically
- Create flake outputs for reusable packages
Success criteria: F12 toggle works correctly in nested scenarios, SSH detection applies appropriate config, no perceivable lag in vim mode switching, team members can clone and use configs immediately.
Estimated time: 5-7 hours (implementation), 3-4 hours (documentation and testing)
Week 4 onwards: Collect feedback from team usage, identify edge cases in daily workflows, monitor performance under real workloads, and iterate on configurations based on actual usage patterns. Consider adding optional features like tmux-resurrect if team requests session persistence, additional status bar widgets based on team needs, or project-specific tmuxp templates for different development stacks.
Beyond vim-tmux-navigator, ensure tight integration between neovim and tmux through shared theme colors, unified clipboard registers, and consistent keybinding philosophy.
Neovim configuration additions:
-- Catppuccin theme matching tmux
require("catppuccin").setup({
flavour = "macchiato",
integrations = {
tmux = true,
},
})
vim.cmd.colorscheme "catppuccin-macchiato"
-- Vim-tmux-navigator plugin
require("lazy").setup({
{
"christoomey/vim-tmux-navigator",
keys = {
{ "<C-h>", "<cmd>TmuxNavigateLeft<cr>" },
{ "<C-j>", "<cmd>TmuxNavigateDown<cr>" },
{ "<C-k>", "<cmd>TmuxNavigateUp<cr>" },
{ "<C-l>", "<cmd>TmuxNavigateRight<cr>" },
},
},
})
-- Disable tmux navigator when zoomed
vim.g.tmux_navigator_disable_when_zoomed = 1
-- OSC 52 clipboard
if os.getenv("TMUX") then
vim.g.clipboard = {
name = 'OSC 52',
copy = {
['+'] = require('vim.ui.clipboard.osc52').copy('+'),
['*'] = require('vim.ui.clipboard.osc52').copy('*'),
},
paste = {
['+'] = require('vim.ui.clipboard.osc52').paste('+'),
['*'] = require('vim.ui.clipboard.osc52').paste('*'),
},
}
endFor per-project development environments, integrate tmux with Nix devShells using direnv for automatic activation.
Pattern 1: Automatic tmuxp loading:
# .envrc in project root
use flake
# Start tmux session automatically
if [ -z "$TMUX" ] && [ -f .tmuxp.yaml ]; then
tmuxp load . &
fiPattern 2: Custom tmux wrapper:
# In devShell
shellHook = ''
alias dev-tmux="tmux -f ${projectTmuxConf}"
echo "Use 'dev-tmux' to start project tmux session"
'';Git-committed files:
.tmuxp.yaml- Project session layoutREADME.mdsection documenting tmux setup- Optional:
.tmux.conf.local.examplefor team-specific customizations
Per-user files (not committed):
~/.config/tmux/local.conf- Individual overrides- Custom status bar widgets
- Personal keybinding preferences
Documentation requirements:
- Prerequisites: Nix with flakes, home-manager version
- Quick start:
nix develop,tmuxp load . - Troubleshooting: Common issues and solutions
- Customization: How to override defaults without modifying shared config
Decision: Pure Nix, no TPM
Rationale: TPM requires runtime git operations, breaking hermetic builds. Nix plugin system provides identical functionality with reproducibility guarantees. The nixpkgs repository includes 57+ plugins covering 90% of use cases. For missing plugins, mkTmuxPlugin enables custom derivations.
Trade-off: Cannot use latest plugin commits without updating SHA256 hashes, but this aligns with Nix philosophy of reproducible builds. Use nix flake update workflow to update plugin versions.
Decision: tmuxp
Rationale: Python-based architecture avoids Ruby version conflicts, better home-manager integration, session freezing capability, config import from multiple formats, and alignment with Python-heavy modern workflows.
Trade-off: Slightly smaller community than tmuxinator (but both have excellent Nix support). Migration path exists for tmuxinator users via tmuxp import.
Decision: vim-tmux-navigator
Rationale: 12+ years of development, largest community, best documentation, Vim and Neovim compatibility, and focused functionality (navigation only). tmux.nvim offers more features but has maintenance concerns and potential performance issues with clipboard sync.
Trade-off: No built-in pane resizing via hjkl. If needed, implement separate resize bindings with Alt-hjkl.
Decision: OSC 52 primary, platform tools fallback
Rationale: OSC 52 works everywhere including SSH without X11 forwarding, nested tmux sessions, and modern terminal emulators. Platform tools (pbcopy, xsel) serve as supplementary mechanisms for local workflows.
Trade-off: Requires terminal emulator support. GNOME Terminal and XFCE Terminal users must rely on platform tools exclusively.
Decision: Catppuccin
Rationale: Official theme with extensive application support (200+ apps), native Nix packaging, active maintenance, four flavor options, and modern aesthetic. Powerline-style separators available via rounded window style.
Trade-off: Less customization than fully custom powerline themes, but 90% of users prefer standard themes for consistency across tools.
Scenario: OSC 52 clipboard doesn't work on user's terminal.
Mitigation: Implement automatic fallback detection to platform-specific tools. Add troubleshooting documentation listing supported terminals. For unsupported terminals, configuration gracefully degrades to xsel/pbcopy without breaking.
Scenario: Long-running tmux sessions lose access to SSH keys after reconnection.
Mitigation: Implement symlink pattern in ~/.ssh/rc for automatic socket renewal. Document shell hook pattern for manual refresh. Provide prefix+$ binding to refresh environment variables.
Scenario: Users don't understand F12 toggle, try to control inner session with outer keybindings.
Mitigation: Prominent visual indicator in status bar showing "OFF" when toggled. Documentation with clear explanation and diagrams. Consider adding brief message on F12 press: "Outer tmux disabled - press F12 to restore".
Scenario: Catppuccin theme breaks after nixpkgs update.
Mitigation: Pin nixpkgs input in flake.lock for stability. Test updates in separate branch before merging to main. Document known-good nixpkgs commit hashes. Consider vendoring critical plugins as custom derivations.
Scenario: Team members prefer existing tmux configurations, resist switching to declarative approach.
Mitigation: Hybrid configuration pattern allowing .tmux.conf.local for personal overrides. Migration guide for importing existing configs. Emphasize benefits: reproducibility, team consistency, zero-setup onboarding. Make adoption gradual and optional initially.
Symptoms: Copy in tmux doesn't reach system clipboard, or paste doesn't work.
Solutions:
- Verify OSC 52 support: Check terminal emulator settings, enable clipboard access in iTerm2/Kitty
- Test with echo:
echo -e "\e]52;c;$(echo -n "test" | base64)\a"should copy "test" to clipboard - Check tmux config: Verify
set -s set-clipboard onis present - Platform tools: Ensure xsel/wl-clipboard (Linux) or pbcopy (macOS) are installed
- Test manually:
tmux save-buffer - | xsel -i --clipboardshould work if platform tool is configured correctly
Symptoms: Ctrl-hjkl doesn't navigate between vim and tmux panes.
Solutions:
- Verify plugin installation: Check both tmux (
tmux list-keys | grep tmux-navigator) and neovim (:echo g:loaded_tmux_navigator) - Check process detection: Run
ps -o state= -o comm= -t '#{pane_tty}'in tmux pane to verify vim is detected - Update patterns: Add your editor to detection regex if using non-standard vim (e.g.,
nvim-qt) - Clear keybinding conflicts: Ensure no other plugins are capturing Ctrl-hjkl
Symptoms: Slow mode switching, visible lag pressing ESC.
Solutions:
- Verify escape-time:
tmux show-options -g | grep escape-timeshould show 0 or small value (10-20) - Apply setting: Run
tmux set-option -g escape-time 0then restart tmux server withtmux kill-server - Check vim: Ensure vim has
set ttimeoutlen=0or equivalent
Symptoms: Boxes or question marks instead of icons, incorrect colors, missing separators.
Solutions:
- Verify Nerd Font: Check terminal font is "FiraCode Nerd Font" or similar patched font
- Test true color: Run color test script, verify RGB support with
tmux info | grep RGB - Check terminal-overrides: Ensure
set -ga terminal-overrides ",xterm-256color:RGB"is present - Restart tmux server: Theme changes require
tmux kill-servernot just reload - Verify Catppuccin config: Check
@catppuccin_flavouris set correctly
Symptoms: Pressing F12 doesn't disable outer tmux session in nested scenarios.
Solutions:
- Verify binding:
tmux list-keys -T root | grep F12should show binding - Check terminal: Some terminals capture F12 before sending to tmux, try F11 instead
- Test manually: Run
tmux set-option -g prefix Noneto verify behavior - Apply configuration: Ensure nested-session.conf is sourced in main config
Symptoms: Colors wrong, clipboard not working, or terminfo errors.
Solutions:
- Set TERM correctly: Use
term = "xterm-256color"in Ghostty config - Enable clipboard: Check Ghostty settings for clipboard access permission
- Terminal features: Use
set -as terminal-features ",xterm*:RGB"for Ghostty - SSH issues: Copy terminfo to remote with
infocmp xterm-ghostty | ssh remote "tic -x -"
Tmux control mode: Research using tmux's -CC flag for programmatic control, enabling custom UIs and advanced automation scripts.
Kitty Graphics Protocol: Investigate rendering images in tmux panes using Ghostty/Kitty's graphics protocol, useful for ML/data science workflows.
AI pair programming integration: Explore tmux layouts optimized for AI assistant workflows (separate pane for context, command suggestions).
Lazy plugin loading: Research conditional plugin loading based on detected context (only load SSH-aware plugins when SSH_CLIENT is set).
Status bar caching: Investigate caching expensive status bar computations (CPU usage, git branch) with smarter invalidation.
Pane rendering optimization: Research tmux 3.4+ rendering improvements and aggressive-resize enhancements.
tmux-navi integration: Explore fuzzy-finding session/window/pane navigation similar to telescope.nvim.
Automated session creation: Research hooks to automatically create project-specific tmux sessions when entering directories with .tmuxp.yaml.
Session templates: Build library of tmuxp templates for common development scenarios (monorepo, microservices, data science, etc.).
Usage analytics: Implement optional telemetry to understand which features are actually used (inform future decisions).
Performance profiling: Create benchmarks for different configuration approaches to quantify performance impacts.
User experience research: Conduct user studies to identify friction points in current workflows.
This specification provides production-ready architecture for implementing tmux in the Kalilix project using declarative Nix flakes and home-manager. The recommended configuration—Ctrl-a prefix, vim-tmux-navigator, Catppuccin Macchiato, tmuxp session management, OSC 52 clipboard, and F12 nested session toggle—represents battle-tested patterns from top configurations analyzed across 20,000+ combined GitHub stars.
Critical success factors:
- Start minimal: Phase 1 foundation before advanced features
- Test incrementally: Verify each feature before adding next
- Document thoroughly: Team adoption depends on clear documentation
- Embrace constraints: Declarative Nix patterns over imperative flexibility
- Iterate based on usage: Real workflows reveal optimization opportunities
Next immediate action: Create modules/devshells/tmux.nix in Kalilix repository, implement Phase 1 core configuration, and test on primary development machine. Success here validates approach for Phases 2 and 3.
The architecture is viable for immediate implementation, technically sound based on extensive research, and positions Kalilix for collaborative refinement. This specification should serve as both implementation guide and reference documentation for the team.