Raycast-style command palette for tmux — fast, scriptable, easy to extend
TypeScript
411
59 commits
updated Jun 24, 2026
A command palette for tmux. It runs on Bun, has no runtime dependencies, and opens quickly enough to use as a regular tmux binding.
Type a few letters, pick a command, hit enter: split a pane, jump to a window,
detach a session, open a popup tool, or switch to a custom palette. User config
lives in ~/.config/tmux-palette/*.json, so local changes survive repo updates.
Commands — main palette for panes, windows, sessions, and built-in tmux actions.
https://github.com/user-attachments/assets/3a8f3951-619f-46b4-a180-b9a03ccb8593
Themes + plugins — theme picker with live preview, plus custom palettes powered by shell commands.
https://github.com/user-attachments/assets/5edce838-9199-4123-8262-352bc47e989c
The tool is in beta. It's stable enough to use day-to-day, but the surface area is still settling — I want the next few weeks to be about running it myself, gathering feedback, and shaking out bugs rather than growing the feature set.
If you'd like to contribute, the most useful work right now is anything that improves the existing base (refactors, perf, polish, docs) or fixes a bug. New features will likely get closed — no hard feelings, but please open an issue first to discuss before writing code.
hidden.jsonTerminal theme that follows your terminal colors. Pick one with live preview, or drop your own{ "popup": "htop" } to open tools like btop, lazygit, log tails, or fzf scripts in a tmux popupexamples/~/.config/tmux-palette/*.jsonlazygit, htop, btop, or log tails in a centered tmux popup.gh pr list that opens PRs in your browser.M-t for Tools or M-a for Appearance.See examples/ for drop-in palettes you can copy into
~/.config/tmux-palette/palettes/.
Use TPM if you already use it; otherwise manual install is the simplest path. The AI-agent option is just a guided setup flow.
display-popup -E support)gh, jq, docker, npm, git, etc.Requires Bun: https://bun.sh
git clone https://github.com/eduwass/tmux-palette ~/Sites/tmux-palette
cd ~/Sites/tmux-palette
bun install
Bind it to a tmux key in your .tmux.conf — Ctrl+Space gives the most "Raycast-feel" since it skips the prefix:
bind -n C-Space run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh"
Or if you'd rather go through the tmux prefix:
bind p run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh"
Reload: tmux source-file ~/.tmux.conf and hit your binding.
Requires Bun: https://bun.sh
Add to your .tmux.conf:
set -g @plugin 'eduwass/tmux-palette'
set -g @palette-key 'C-Space' # optional, default: C-Space (no-prefix)
set -g @palette-find-pane-key 'M-f' # optional, no binding by default
set -g @palette-move-pane-key 'M-m' # optional, no binding by default
Then prefix + I (TPM's install key) to install. TPM clones the repo,
runs bun install on first load, and binds the keys for you. Set
@palette-key 'off' to skip the main binding and bind it yourself.
This is optional; it is just a guided setup flow. Choose it if you want an agent to drive the onboarding experience: install the repo, set up the tmux binding, test that it opens, and optionally create your first custom commands or theme.
Paste the prompt below into Claude Code, Codex, opencode, Cursor, or any AI coding agent.
You are helping a user onboard tmux-palette, a small command palette for tmux. Repo: https://github.com/eduwass/tmux-palette
Goal: get the palette installed, bound to a key, tested inside tmux, and leave the user with one useful next customization if they want it.
Follow steps in order. Confirm with the user before any change that modifies their files.
1. Prerequisites
- Run `bun --version`. If Bun is missing, point them to https://bun.sh/docs/installation and stop — do not auto-install.
- Run `tmux -V`. If lower than 3.4, warn that `display-popup -E` may not work, then proceed.
2. Clone and install
- Default path: `~/Sites/tmux-palette`. Ask the user if they want a different location.
- If the path already exists and contains the repo, run `git -C <path> pull` and skip cloning.
- Otherwise: `git clone https://github.com/eduwass/tmux-palette <path> && cd <path> && bun install`.
3. Bind it to a tmux key (required — the palette doesn't open without one)
- Default suggestion: `bind -n C-Space run-shell "<absolute-path>/bin/tmux-palette.sh"` (no-prefix, opens with Ctrl+Space). Ask the user if they want a different key.
- Append the bind line to `~/.tmux.conf` (create it if missing).
- Run `tmux source-file ~/.tmux.conf` to reload (or tell them to do it).
4. Pick a theme (optional)
Ask: "Want to choose a built-in theme now, or should I try to match your terminal's theme?"
If they want a built-in theme:
- Tell them they can open the palette and choose **Switch Theme...** for live preview.
- Mention the curated built-ins: Shades of Purple, Dracula, Tokyo Night, Catppuccin Mocha, Gruvbox Dark, Rosé Pine, Nord, Solarized Dark, Kanagawa Wave, GitHub Dark, One Dark, Ayu Dark.
If they want to match their terminal, detect it:
- Check $TERM_PROGRAM and $TERM. Common values: ghostty, iTerm.app, vscode, WezTerm, Apple_Terminal.
- Read the relevant config:
- Ghostty: ~/.config/ghostty/config
- Alacritty: ~/.config/alacritty/alacritty.toml (or .yml)
- Kitty: ~/.config/kitty/kitty.conf (follow `include` lines)
- WezTerm: ~/.wezterm.lua or ~/.config/wezterm/wezterm.lua
- iTerm2 / others: ask the user for hex codes; their configs are hard to parse.
- Extract: background → `bg`, foreground → `fg`, cursor color → `accent`, selection bg → `selected`. Derive `panel` (slightly lighter than bg) and `muted` (fg dimmed).
- Write `~/.config/tmux-palette/theme.json` with `{ bg, panel, selected, fg, muted, accent }`. The palette reads this at runtime; do NOT edit source files.
- Report the colors you picked.
5. Test
Tell the user to press their binding. Ask what they see.
6. Offer one useful follow-up
When it works, ask:
- "Want a quick custom command, like opening lazygit or htop in a popup?" — write items to `~/.config/tmux-palette/commands.json` (array of Items). Action types: `{ "tmux": "..." }`, `{ "shell": "..." }`, `{ "popup": "..." }`, `{ "palette": "name" }`. Do NOT edit source files.
- "Want a focused palette for PRs, Docker logs, npm scripts, or git branches?" — copy an example from `examples/` into `~/.config/tmux-palette/palettes/` and bind it.
- "Want custom shortcut labels?" — write `~/.config/tmux-palette/shortcuts.json` mapping item titles to label strings.
Only do one follow-up unless the user asks for more.
Constraints
- Prefer `~/.config/tmux-palette/*.json` over source edits. The user's config survives upstream pulls; source edits don't.
- Do not push to git or modify files outside the user's home directory.
- Do not auto-install Bun or any other system package.
- If anything fails, stop and explain what went wrong.
split horiz).vimKeys in navigation.json for Ctrl+J/K (down/up) and Ctrl+U/D (page), replacing the search-box bindings for those keys.Auto-aliases: initials of multi-word titles match automatically. Type nw for "New
Window", cs for "Choose Session", sh for "Split Horizontal", etc.
bun test, TypeScript, Fallow dead-code, and Fallow duplication checks.~/.config/tmux-palette/; normal customization should not
require editing source files.{ "shell": "..." } and { "popup": "..." } actions execute through the user's
shell by design.Drop-in user config lives in ~/.config/tmux-palette/. One JSON file per
concern — no source edits, no fork, survives upstream pulls.
Path: ~/.config/tmux-palette/palettes/<name>.json
Define a brand-new palette and bind any key to its name:
bind -n M-q run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh my-favs"
// ~/.config/tmux-palette/palettes/my-favs.json
{
"title": "Favorites",
"from": ["Toggle Diff Viewer", "Find Pane", "Choose Session"],
"fromCategory": "Tools",
"items": [
{
"icon": "",
"title": "Custom item only in this palette",
"action": { "tmux": "run-shell '~/scripts/x.sh'" },
},
],
}
from — array of item titles to pull from the main commands palette (built-ins + your commands.json)fromCategory — pull every item from one categorycommand — shell command that prints a JSON array of Item objects to stdout (see plugins below)items — brand-new items defined inlinetitle / grouped / emptyText — same as built-in palettesAll keys optional. Resolution order: from → fromCategory → command → items.
Drop-in ready-to-use palettes live in
examples/— git branches, GitHub PRs (with color-coded status dots), Docker container logs, npm scripts, file picker. Copy one and bind a key.
The command field is the plugin escape hatch — fzf-style. Anything
that prints to stdout becomes a palette. Two output modes:
JSON mode — full control. Print a JSON array of Item objects:
// ~/.config/tmux-palette/palettes/github-prs.json
{
"title": "GitHub PRs",
"command": "gh pr list --json number,title,url --jq '[.[] | {icon: \"\", title: ((.number|tostring) + \" \" + .title), action: {shell: (\"gh pr view \" + (.number|tostring) + \" --web\")}}]'",
}
Plain-text mode — fzf-style. Print one item per line, define a
default action template at the palette level with {} substituted
for the selected line:
// ~/.config/tmux-palette/palettes/git-branches.json
{
"title": "Git Branches",
"command": "git branch --format='%(refname:short)'",
"action": { "tmux": "send-keys 'git checkout {}' Enter" },
}
This means most "command that prints lines" tools you'd pipe through fzf can become tmux-palette palettes with no scripting — just the JSON config and an action template.
Write the command in any language, distribute it however you want (gist, repo, copy-paste). The plugin runs every time the palette opens (no caching), so for expensive calls add your own cache layer. Errors are surfaced as a single item in the palette so failures stay visible without crashing the popup.
hidden.json — hide built-in itemsDrop a JSON array of item titles to skip them in the main commands palette:
["Toggle Status Bar", "Reload Config", "Toggle OpenTUI Top Bar"]
Items still appear if you reference them by title in a custom palette
(see above) — hidden.json is just about decluttering the default.
commands.json — your own itemsAppend items to the commands palette without editing source:
[
{
"icon": "",
"title": "Toggle Diff Viewer",
"category": "Tools",
"action": { "tmux": "run-shell '/path/to/script.sh'" }
},
{
"icon": "",
"title": "Open Project in Cursor",
"category": "Tools",
"action": { "shell": "cursor /path/to/project" }
},
{
"icon": "",
"title": "htop",
"category": "Tools",
"action": { "popup": "htop" }
}
]
Action types: { "tmux": "..." }, { "shell": "..." }, { "popup": "..." }, { "palette": "find-pane" }.
{ "popup": "htop" } opens the given command in a centered tmux popup
(80% × 80%, closes when the command exits). Handy for log viewers,
htop, btop, less, fzf-driven tools, etc.
navigation.json — list navigation behavior{
"wrapAtListEnds": true,
"vimKeys": true
}
All keys optional. wrapAtListEnds controls list navigation at the first
and last selectable items. It defaults to true, so moving past either
end wraps around. Set it to false to make the beginning and end hard
stops.
vimKeys adds Ctrl+J/K for moving down/up and Ctrl+U/D for paging,
replacing the search-box edit bindings for those keys. Default: false.
sizing.json — popup dimensions and borders{
"maxHeight": 28,
"width": 90,
"padX": 3,
"mobileWidth": 80,
"border": "none",
"popupBorder": "none"
}
All keys optional. maxHeight caps how tall the popup gets when you
have lots of commands. width is the fixed popup width. padX is the
horizontal padding inside the popup.
mobileWidth is the client-width threshold for auto-fullscreen mode:
when the terminal is narrower than this many columns (iOS terminals
like Blink or Moshi typically run 50-60 cols), the popup goes
edge-to-edge with padX=1. Defaults to 80, set to 0 to disable.
border is the main palette border, popupBorder is the border for
{ "popup": "..." } action popups. Both default to none. Accepted
values: none, single, double, heavy, rounded, padded,
simple — passed straight to tmux display-popup -b. rounded works
but its corner glyphs can leave small visual gaps against the
surrounding terminal, so it is not the default.
Open the main palette and pick Switch Theme... (under Appearance). Arrow-key through the list — every theme lives-previews instantly so you see the colors apply before you commit. Enter saves it and returns you to the previous palette with the new theme on; Esc cancels.
The theme picker also includes Add custom theme..., which opens this README's custom-theme instructions in your default browser.
Bundled themes are intentionally limited to a small curated set: Shades of
Purple, Dracula, Tokyo Night, Catppuccin Mocha, Gruvbox Dark, Rosé Pine, Nord,
Solarized Dark, Kanagawa Wave, GitHub Dark, One Dark, and Ayu Dark. Their
panel/selected/muted/accent colors are tuned for readable contrast. To tweak a
theme without editing the repo, add a custom theme file or override individual
colors in theme.json.
theme.json — set the active themeTwo forms. Pick a bundled (or custom) theme by name:
{ "name": "tokyo-night" }
Or full color override (applied on top of the resolved theme):
{
"bg": "#1a1b26",
"panel": "#16161e",
"selected": "#283457",
"fg": "#c0caf5",
"muted": "#565f89",
"accent": "#7aa2f7"
}
The theme switcher writes the { "name": "..." } form for you. You can
also mix: pick a name, then add individual keys to nudge specific colors.
Drop one JSON file per theme into ~/.config/tmux-palette/themes/:
// ~/.config/tmux-palette/themes/my-theme.json
{
"bg": "#0d0f12",
"panel": "#171a1f",
"selected": "#2c3038",
"fg": "#e6e8eb",
"muted": "#7d8590",
"accent": "#ff6b6b",
}
Custom themes show up in the Switch Theme... picker alongside the
bundled ones (tagged custom). The filename becomes the slug — drop
my-theme.json → reference it as { "name": "my-theme" } in
theme.json, or just pick it from the switcher.
Any color field accepts more than a hex code:
"transparent" — falls back to the terminal default. On bg/panel/
selected the terminal's own background (transparency or image) shows
through; on fg/muted/accent it uses the terminal's default text color."black", "red", "green", "yellow",
"blue", "magenta", "cyan", "white", or any of those with a
"bright-" prefix ("bright-black" is the usual gray). These render with the
terminal's own configured colors, so the palette tracks whatever
colorscheme you're running instead of pinning a fixed hex.The bundled terminal theme ({ "name": "terminal" }) leans on these:
transparent backgrounds, default-foreground item and secondary text, blue
icons and title, and a highlighted row that turns yellow — so it adapts to
your terminal's palette automatically in both light and dark modes.
There are also two optional foreground fields. selectedFg gives the
highlighted row a single distinct color for its icon, title, marker, and
shortcut; when omitted the active row keeps the defaults (title on fg,
marker/icon on accent). titleFg colors the popup title in the header
(e.g. "Commands") independently of the search input; when omitted the title
uses fg.
// ~/.config/tmux-palette/themes/ghost.json — terminal-native theme
{
"bg": "transparent",
"panel": "transparent",
"selected": "transparent",
"fg": "transparent",
"muted": "bright-black",
"accent": "blue",
"selectedFg": "yellow",
}
Pass --category=<name> to open the main palette filtered to one
category, Raycast-favorites style:
bind -n M-t run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh commands --category=Tools"
bind -n M-a run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh commands --category=Appearance"
The popup title auto-updates to the category name and the category header gets hidden (since everything is the same category anyway).
shortcuts.json — custom shortcut labelsWhen your terminal has a key-remap layer (Ghostty / iTerm2 / Karabiner) that
translates something like Cmd+D into a tmux binding, tmux only sees the
tmux side and doesn't know the original key. Use this to show what you
actually press:
{
"Split Horizontal": "Cmd+D",
"Find Pane": "Cmd+Shift+P",
"Choose Session": "Cmd+S"
}
Keys are item titles; values are whatever text you want on the right side.
aliases.json — extra visible alias chips{
"Split Horizontal": ["sh"],
"Find Pane": ["fp"]
}
Auto-aliases (initials like nw) still work for free, invisibly.
For things JSON can't express — custom row rendering, dynamic item
generators, custom filter logic — edit the TS source. Items in
src/palettes/commands.ts have this shape:
{
icon: "", // any nerd-font glyph
title: "Find Pane",
description?: "...", // optional, dimmed text after title
shortcut?: "Cmd+Shift+P", // optional, right-aligned label
category?: "Panes", // optional, groups items under a header
aliases?: ["fp"], // optional, visible chip + searchable
action: { tmux: "..." } // see Actions below
}
{ tmux: "split-window -h" } // runs `tmux <cmd>` after the popup closes
{ shell: "echo hi" } // runs a shell command after the popup closes
{ popup: "htop" } // opens cmd in a centered 80% tmux popup
{ palette: "find-pane" } // chains into another palette
{ run: (ctx) => { ... } } // custom JS, runs in-process, then exits
{ apply: (ctx) => { ... } } // custom JS, runs in-process, then pops
// back to the previous palette (used by
// the theme switcher to "apply + return")
{ tmux } is special: it dispatches after the popup closes, so interactive
tmux prompts (confirm-before, command-prompt) actually get keyboard
input. Without this, prompts hang because the popup still owns stdin.
The bash wrapper opens a tmux display-popup running the palette. When you
pick an item, the palette writes the encoded command to a tempfile and exits.
The wrapper then reads the tempfile and runs the command — after the
popup is gone. This matters because interactive tmux commands like
confirm-before need stdin, which is captured by the popup while it's open.
MIT
113 followers · starred May 2026
515 followers · starred May 2026
741 followers · starred May 2026
673 followers · starred May 2026
Raycast-style command palette for tmux — fast, scriptable, easy to extend
TypeScript
411
59 commits
updated Jun 24, 2026
A command palette for tmux. It runs on Bun, has no runtime dependencies, and opens quickly enough to use as a regular tmux binding.
Type a few letters, pick a command, hit enter: split a pane, jump to a window,
detach a session, open a popup tool, or switch to a custom palette. User config
lives in ~/.config/tmux-palette/*.json, so local changes survive repo updates.
Commands — main palette for panes, windows, sessions, and built-in tmux actions.
https://github.com/user-attachments/assets/3a8f3951-619f-46b4-a180-b9a03ccb8593
Themes + plugins — theme picker with live preview, plus custom palettes powered by shell commands.
https://github.com/user-attachments/assets/5edce838-9199-4123-8262-352bc47e989c
The tool is in beta. It's stable enough to use day-to-day, but the surface area is still settling — I want the next few weeks to be about running it myself, gathering feedback, and shaking out bugs rather than growing the feature set.
If you'd like to contribute, the most useful work right now is anything that improves the existing base (refactors, perf, polish, docs) or fixes a bug. New features will likely get closed — no hard feelings, but please open an issue first to discuss before writing code.
hidden.jsonTerminal theme that follows your terminal colors. Pick one with live preview, or drop your own{ "popup": "htop" } to open tools like btop, lazygit, log tails, or fzf scripts in a tmux popupexamples/~/.config/tmux-palette/*.jsonlazygit, htop, btop, or log tails in a centered tmux popup.gh pr list that opens PRs in your browser.M-t for Tools or M-a for Appearance.See examples/ for drop-in palettes you can copy into
~/.config/tmux-palette/palettes/.
Use TPM if you already use it; otherwise manual install is the simplest path. The AI-agent option is just a guided setup flow.
display-popup -E support)gh, jq, docker, npm, git, etc.Requires Bun: https://bun.sh
git clone https://github.com/eduwass/tmux-palette ~/Sites/tmux-palette
cd ~/Sites/tmux-palette
bun install
Bind it to a tmux key in your .tmux.conf — Ctrl+Space gives the most "Raycast-feel" since it skips the prefix:
bind -n C-Space run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh"
Or if you'd rather go through the tmux prefix:
bind p run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh"
Reload: tmux source-file ~/.tmux.conf and hit your binding.
Requires Bun: https://bun.sh
Add to your .tmux.conf:
set -g @plugin 'eduwass/tmux-palette'
set -g @palette-key 'C-Space' # optional, default: C-Space (no-prefix)
set -g @palette-find-pane-key 'M-f' # optional, no binding by default
set -g @palette-move-pane-key 'M-m' # optional, no binding by default
Then prefix + I (TPM's install key) to install. TPM clones the repo,
runs bun install on first load, and binds the keys for you. Set
@palette-key 'off' to skip the main binding and bind it yourself.
This is optional; it is just a guided setup flow. Choose it if you want an agent to drive the onboarding experience: install the repo, set up the tmux binding, test that it opens, and optionally create your first custom commands or theme.
Paste the prompt below into Claude Code, Codex, opencode, Cursor, or any AI coding agent.
You are helping a user onboard tmux-palette, a small command palette for tmux. Repo: https://github.com/eduwass/tmux-palette
Goal: get the palette installed, bound to a key, tested inside tmux, and leave the user with one useful next customization if they want it.
Follow steps in order. Confirm with the user before any change that modifies their files.
1. Prerequisites
- Run `bun --version`. If Bun is missing, point them to https://bun.sh/docs/installation and stop — do not auto-install.
- Run `tmux -V`. If lower than 3.4, warn that `display-popup -E` may not work, then proceed.
2. Clone and install
- Default path: `~/Sites/tmux-palette`. Ask the user if they want a different location.
- If the path already exists and contains the repo, run `git -C <path> pull` and skip cloning.
- Otherwise: `git clone https://github.com/eduwass/tmux-palette <path> && cd <path> && bun install`.
3. Bind it to a tmux key (required — the palette doesn't open without one)
- Default suggestion: `bind -n C-Space run-shell "<absolute-path>/bin/tmux-palette.sh"` (no-prefix, opens with Ctrl+Space). Ask the user if they want a different key.
- Append the bind line to `~/.tmux.conf` (create it if missing).
- Run `tmux source-file ~/.tmux.conf` to reload (or tell them to do it).
4. Pick a theme (optional)
Ask: "Want to choose a built-in theme now, or should I try to match your terminal's theme?"
If they want a built-in theme:
- Tell them they can open the palette and choose **Switch Theme...** for live preview.
- Mention the curated built-ins: Shades of Purple, Dracula, Tokyo Night, Catppuccin Mocha, Gruvbox Dark, Rosé Pine, Nord, Solarized Dark, Kanagawa Wave, GitHub Dark, One Dark, Ayu Dark.
If they want to match their terminal, detect it:
- Check $TERM_PROGRAM and $TERM. Common values: ghostty, iTerm.app, vscode, WezTerm, Apple_Terminal.
- Read the relevant config:
- Ghostty: ~/.config/ghostty/config
- Alacritty: ~/.config/alacritty/alacritty.toml (or .yml)
- Kitty: ~/.config/kitty/kitty.conf (follow `include` lines)
- WezTerm: ~/.wezterm.lua or ~/.config/wezterm/wezterm.lua
- iTerm2 / others: ask the user for hex codes; their configs are hard to parse.
- Extract: background → `bg`, foreground → `fg`, cursor color → `accent`, selection bg → `selected`. Derive `panel` (slightly lighter than bg) and `muted` (fg dimmed).
- Write `~/.config/tmux-palette/theme.json` with `{ bg, panel, selected, fg, muted, accent }`. The palette reads this at runtime; do NOT edit source files.
- Report the colors you picked.
5. Test
Tell the user to press their binding. Ask what they see.
6. Offer one useful follow-up
When it works, ask:
- "Want a quick custom command, like opening lazygit or htop in a popup?" — write items to `~/.config/tmux-palette/commands.json` (array of Items). Action types: `{ "tmux": "..." }`, `{ "shell": "..." }`, `{ "popup": "..." }`, `{ "palette": "name" }`. Do NOT edit source files.
- "Want a focused palette for PRs, Docker logs, npm scripts, or git branches?" — copy an example from `examples/` into `~/.config/tmux-palette/palettes/` and bind it.
- "Want custom shortcut labels?" — write `~/.config/tmux-palette/shortcuts.json` mapping item titles to label strings.
Only do one follow-up unless the user asks for more.
Constraints
- Prefer `~/.config/tmux-palette/*.json` over source edits. The user's config survives upstream pulls; source edits don't.
- Do not push to git or modify files outside the user's home directory.
- Do not auto-install Bun or any other system package.
- If anything fails, stop and explain what went wrong.
split horiz).vimKeys in navigation.json for Ctrl+J/K (down/up) and Ctrl+U/D (page), replacing the search-box bindings for those keys.Auto-aliases: initials of multi-word titles match automatically. Type nw for "New
Window", cs for "Choose Session", sh for "Split Horizontal", etc.
bun test, TypeScript, Fallow dead-code, and Fallow duplication checks.~/.config/tmux-palette/; normal customization should not
require editing source files.{ "shell": "..." } and { "popup": "..." } actions execute through the user's
shell by design.Drop-in user config lives in ~/.config/tmux-palette/. One JSON file per
concern — no source edits, no fork, survives upstream pulls.
Path: ~/.config/tmux-palette/palettes/<name>.json
Define a brand-new palette and bind any key to its name:
bind -n M-q run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh my-favs"
// ~/.config/tmux-palette/palettes/my-favs.json
{
"title": "Favorites",
"from": ["Toggle Diff Viewer", "Find Pane", "Choose Session"],
"fromCategory": "Tools",
"items": [
{
"icon": "",
"title": "Custom item only in this palette",
"action": { "tmux": "run-shell '~/scripts/x.sh'" },
},
],
}
from — array of item titles to pull from the main commands palette (built-ins + your commands.json)fromCategory — pull every item from one categorycommand — shell command that prints a JSON array of Item objects to stdout (see plugins below)items — brand-new items defined inlinetitle / grouped / emptyText — same as built-in palettesAll keys optional. Resolution order: from → fromCategory → command → items.
Drop-in ready-to-use palettes live in
examples/— git branches, GitHub PRs (with color-coded status dots), Docker container logs, npm scripts, file picker. Copy one and bind a key.
The command field is the plugin escape hatch — fzf-style. Anything
that prints to stdout becomes a palette. Two output modes:
JSON mode — full control. Print a JSON array of Item objects:
// ~/.config/tmux-palette/palettes/github-prs.json
{
"title": "GitHub PRs",
"command": "gh pr list --json number,title,url --jq '[.[] | {icon: \"\", title: ((.number|tostring) + \" \" + .title), action: {shell: (\"gh pr view \" + (.number|tostring) + \" --web\")}}]'",
}
Plain-text mode — fzf-style. Print one item per line, define a
default action template at the palette level with {} substituted
for the selected line:
// ~/.config/tmux-palette/palettes/git-branches.json
{
"title": "Git Branches",
"command": "git branch --format='%(refname:short)'",
"action": { "tmux": "send-keys 'git checkout {}' Enter" },
}
This means most "command that prints lines" tools you'd pipe through fzf can become tmux-palette palettes with no scripting — just the JSON config and an action template.
Write the command in any language, distribute it however you want (gist, repo, copy-paste). The plugin runs every time the palette opens (no caching), so for expensive calls add your own cache layer. Errors are surfaced as a single item in the palette so failures stay visible without crashing the popup.
hidden.json — hide built-in itemsDrop a JSON array of item titles to skip them in the main commands palette:
["Toggle Status Bar", "Reload Config", "Toggle OpenTUI Top Bar"]
Items still appear if you reference them by title in a custom palette
(see above) — hidden.json is just about decluttering the default.
commands.json — your own itemsAppend items to the commands palette without editing source:
[
{
"icon": "",
"title": "Toggle Diff Viewer",
"category": "Tools",
"action": { "tmux": "run-shell '/path/to/script.sh'" }
},
{
"icon": "",
"title": "Open Project in Cursor",
"category": "Tools",
"action": { "shell": "cursor /path/to/project" }
},
{
"icon": "",
"title": "htop",
"category": "Tools",
"action": { "popup": "htop" }
}
]
Action types: { "tmux": "..." }, { "shell": "..." }, { "popup": "..." }, { "palette": "find-pane" }.
{ "popup": "htop" } opens the given command in a centered tmux popup
(80% × 80%, closes when the command exits). Handy for log viewers,
htop, btop, less, fzf-driven tools, etc.
navigation.json — list navigation behavior{
"wrapAtListEnds": true,
"vimKeys": true
}
All keys optional. wrapAtListEnds controls list navigation at the first
and last selectable items. It defaults to true, so moving past either
end wraps around. Set it to false to make the beginning and end hard
stops.
vimKeys adds Ctrl+J/K for moving down/up and Ctrl+U/D for paging,
replacing the search-box edit bindings for those keys. Default: false.
sizing.json — popup dimensions and borders{
"maxHeight": 28,
"width": 90,
"padX": 3,
"mobileWidth": 80,
"border": "none",
"popupBorder": "none"
}
All keys optional. maxHeight caps how tall the popup gets when you
have lots of commands. width is the fixed popup width. padX is the
horizontal padding inside the popup.
mobileWidth is the client-width threshold for auto-fullscreen mode:
when the terminal is narrower than this many columns (iOS terminals
like Blink or Moshi typically run 50-60 cols), the popup goes
edge-to-edge with padX=1. Defaults to 80, set to 0 to disable.
border is the main palette border, popupBorder is the border for
{ "popup": "..." } action popups. Both default to none. Accepted
values: none, single, double, heavy, rounded, padded,
simple — passed straight to tmux display-popup -b. rounded works
but its corner glyphs can leave small visual gaps against the
surrounding terminal, so it is not the default.
Open the main palette and pick Switch Theme... (under Appearance). Arrow-key through the list — every theme lives-previews instantly so you see the colors apply before you commit. Enter saves it and returns you to the previous palette with the new theme on; Esc cancels.
The theme picker also includes Add custom theme..., which opens this README's custom-theme instructions in your default browser.
Bundled themes are intentionally limited to a small curated set: Shades of
Purple, Dracula, Tokyo Night, Catppuccin Mocha, Gruvbox Dark, Rosé Pine, Nord,
Solarized Dark, Kanagawa Wave, GitHub Dark, One Dark, and Ayu Dark. Their
panel/selected/muted/accent colors are tuned for readable contrast. To tweak a
theme without editing the repo, add a custom theme file or override individual
colors in theme.json.
theme.json — set the active themeTwo forms. Pick a bundled (or custom) theme by name:
{ "name": "tokyo-night" }
Or full color override (applied on top of the resolved theme):
{
"bg": "#1a1b26",
"panel": "#16161e",
"selected": "#283457",
"fg": "#c0caf5",
"muted": "#565f89",
"accent": "#7aa2f7"
}
The theme switcher writes the { "name": "..." } form for you. You can
also mix: pick a name, then add individual keys to nudge specific colors.
Drop one JSON file per theme into ~/.config/tmux-palette/themes/:
// ~/.config/tmux-palette/themes/my-theme.json
{
"bg": "#0d0f12",
"panel": "#171a1f",
"selected": "#2c3038",
"fg": "#e6e8eb",
"muted": "#7d8590",
"accent": "#ff6b6b",
}
Custom themes show up in the Switch Theme... picker alongside the
bundled ones (tagged custom). The filename becomes the slug — drop
my-theme.json → reference it as { "name": "my-theme" } in
theme.json, or just pick it from the switcher.
Any color field accepts more than a hex code:
"transparent" — falls back to the terminal default. On bg/panel/
selected the terminal's own background (transparency or image) shows
through; on fg/muted/accent it uses the terminal's default text color."black", "red", "green", "yellow",
"blue", "magenta", "cyan", "white", or any of those with a
"bright-" prefix ("bright-black" is the usual gray). These render with the
terminal's own configured colors, so the palette tracks whatever
colorscheme you're running instead of pinning a fixed hex.The bundled terminal theme ({ "name": "terminal" }) leans on these:
transparent backgrounds, default-foreground item and secondary text, blue
icons and title, and a highlighted row that turns yellow — so it adapts to
your terminal's palette automatically in both light and dark modes.
There are also two optional foreground fields. selectedFg gives the
highlighted row a single distinct color for its icon, title, marker, and
shortcut; when omitted the active row keeps the defaults (title on fg,
marker/icon on accent). titleFg colors the popup title in the header
(e.g. "Commands") independently of the search input; when omitted the title
uses fg.
// ~/.config/tmux-palette/themes/ghost.json — terminal-native theme
{
"bg": "transparent",
"panel": "transparent",
"selected": "transparent",
"fg": "transparent",
"muted": "bright-black",
"accent": "blue",
"selectedFg": "yellow",
}
Pass --category=<name> to open the main palette filtered to one
category, Raycast-favorites style:
bind -n M-t run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh commands --category=Tools"
bind -n M-a run-shell "~/Sites/tmux-palette/bin/tmux-palette.sh commands --category=Appearance"
The popup title auto-updates to the category name and the category header gets hidden (since everything is the same category anyway).
shortcuts.json — custom shortcut labelsWhen your terminal has a key-remap layer (Ghostty / iTerm2 / Karabiner) that
translates something like Cmd+D into a tmux binding, tmux only sees the
tmux side and doesn't know the original key. Use this to show what you
actually press:
{
"Split Horizontal": "Cmd+D",
"Find Pane": "Cmd+Shift+P",
"Choose Session": "Cmd+S"
}
Keys are item titles; values are whatever text you want on the right side.
aliases.json — extra visible alias chips{
"Split Horizontal": ["sh"],
"Find Pane": ["fp"]
}
Auto-aliases (initials like nw) still work for free, invisibly.
For things JSON can't express — custom row rendering, dynamic item
generators, custom filter logic — edit the TS source. Items in
src/palettes/commands.ts have this shape:
{
icon: "", // any nerd-font glyph
title: "Find Pane",
description?: "...", // optional, dimmed text after title
shortcut?: "Cmd+Shift+P", // optional, right-aligned label
category?: "Panes", // optional, groups items under a header
aliases?: ["fp"], // optional, visible chip + searchable
action: { tmux: "..." } // see Actions below
}
{ tmux: "split-window -h" } // runs `tmux <cmd>` after the popup closes
{ shell: "echo hi" } // runs a shell command after the popup closes
{ popup: "htop" } // opens cmd in a centered 80% tmux popup
{ palette: "find-pane" } // chains into another palette
{ run: (ctx) => { ... } } // custom JS, runs in-process, then exits
{ apply: (ctx) => { ... } } // custom JS, runs in-process, then pops
// back to the previous palette (used by
// the theme switcher to "apply + return")
{ tmux } is special: it dispatches after the popup closes, so interactive
tmux prompts (confirm-before, command-prompt) actually get keyboard
input. Without this, prompts hang because the popup still owns stdin.
The bash wrapper opens a tmux display-popup running the palette. When you
pick an item, the palette writes the encoded command to a tempfile and exits.
The wrapper then reads the tempfile and runs the command — after the
popup is gone. This matters because interactive tmux commands like
confirm-before need stdin, which is captured by the popup while it's open.
MIT
113 followers · starred May 2026
515 followers · starred May 2026
741 followers · starred May 2026
673 followers · starred May 2026