Configuration
ZenTerm reads config from ~/.config/zen-term/config. The format is Ghostty-flavored key = value, and every value below is the built-in default, so an empty file behaves exactly like this. Copy the annotated sample and uncomment what you want to change.
Theme
themedefaultrose-pine-zenSelect a named theme from ~/.config/zen-term/themes/. Keep a library there and switch with this one line. Each file is ghostty-format (drop in any ghostty theme unchanged); it recolors both the terminal and the chrome. Unset → a legacy single ~/.config/zen-term/theme file if present, else Rosé Pine Zen. A name that doesn't exist warns and falls back to that default. See docs/config/themes/ for an example. The app also ships a bundled theme catalog, selectable in Settings → Appearance → Theme without hand-editing this file. A name resolves a user themes/<name> file first, else a bundled theme. The picker writes this same key; restart the app to apply the change.
accent-colordefaultblueThe chrome's accent: the color for focus, active state, and confirm. It paints the focused pane's glow and border, focus rings, the active tab's underline, a tool's activity dot, primary buttons, and the progress bar. Pick any slot of the theme's palette and the whole chrome follows; the terminal is untouched, so a program printing in that color is unaffected. The names are the usual ANSI hues, so what you get is whatever your theme put in that slot: on Rosé Pine Zen, magenta is iris. Settings → Appearance → Accent color shows each one's swatch and hex. Unset → blue (palette 4). black red green yellow blue magenta cyan white and a bright- variant of each (bright-red, bright-cyan, …)
Terminal appearance
font-familydefaultJetBrainsMono Nerd Font MonoMonospaced font for the terminal surfaces. (Colors live in a theme file, not here. See the Theme section above.)
font-sizedefault14points; clamped to 6…32. ⌘+ / ⌘- step this for theMonospaced font for the terminal surfaces. (Colors live in a theme file, not here. See the Theme section above.)
font-thickendefaultfalsetrue | falseFake-bold every glyph by dilating its coverage. Off matches stock ghostty. On is heavier than it sounds: the thickening strength is maxed out, so on a Retina panel it reads as a permanent bold rather than a nudge. In-app Settings edit: applies live.
cursor-styledefaultblockblock | bar | underlineCursor shape and blink.
cursor-style-blinkdefaulttruetrue | falseCursor shape and blink.
cursor-thicknessdefault2clamped to 1…12Cursor thickness in px for the bar/underline styles (block fills the cell, so it ignores this). ghostty's base is a nearly invisible 1px, so the default is 2.
macos-option-as-altdefaulttruetrue | falseTreat the ⌥ (Option) key as Alt/Meta: enables ⌥f/⌥b word-nav in readline and Meta chords in vim/emacs, at the cost of macOS accent composing. true = Meta.
scroll-multiplierdefault1.5clamped to 0.1…10Precise-scroll (trackpad) feel multiplier. 1 = raw, higher = faster.
cursor-shaderdefaultcursor_warpcursor_warp | cursor_tailA bundled GLSL cursor shader drawn over the terminal surface. Bundled, vetted options only, one at a time: `cursor_warp` is a pronounced Neovide-style smear, `cursor_tail` a lighter comet. Unset = none. Also in Settings → Terminal → Cursor shader, which applies live. These are cursor effects, so they show on cursor moves and read most in editors like neovim; they do not affect scrolling. A shader redraws the whole terminal surface every frame while a pane is focused, so what it costs scales with the pixels on screen. On a 5K display that measured as most of the GPU. Drawing stops when ZenTerm is not frontmost, and when the window is covered, so a shader costs nothing in the background.
background-alphadefault1clamped to 0…1Translucency of the terminal background. 1 = solid, 0 = your text over the window blur with the desktop behind it. The inner padding around a terminal follows the same value, in a pane and in a tool float, so each reads as one surface. A float frosts the panes it covers, the way a pane frosts the desktop. Cards stay solid: toasts, Settings, the palette. Set `backdrop-alpha` below to dial the window shell. In-app Settings edit: applies live.
Chrome
window-chromedefaulttruetrue | falseShow the standard macOS window buttons (close, minimize, zoom) and reserve the header space that clears them. Off = a chromeless top; close/minimize still work via ⌘W / ⌘M and the menu, and the window drags by its background. In-app Settings edit: applies live.
hide-toolbar-buttonsdefaulte.g. split-h,split-vHide any of the footer toolbar's built-in buttons (bottom-right of the tab bar). Comma-separated, any order. Hiding is visual only: every hidden button's shortcut and palette entry stay live. Buttons, in toolbar order: new-tab, split-h, split-v, bottom-drawer, right-drawer, scratch, focus-mode, command-palette In-app Settings edit: applies live.
backdrop-alphadefault0.82clamped to 0…1Opacity of the on-brand tint over the behind-window blur. 1 = solid shell, 0 = raw system blur showing your desktop through the whole window. Below 1, part of what you see through the shell is your desktop, and the chrome on top of it (the footer icons, the tab titles) reads against that. A strongly contrasting wallpaper costs some of their legibility, which is the trade a lower value buys. Set 1 for a solid shell if you would rather keep the contrast. In-app Settings edit: applies live.
window-gutterdefault8clamped to 0…64Inset from the window edge to the tiled panes, all four sides (px). In-app Settings edit: applies live.
pane-gapdefault8clamped to 0…64Gap between tiled panes, and between the canvas and a drawer (px). In-app Settings edit: applies live.
bottom-drawer-fractiondefault0.28clamped to 0.1…0.9Initial size a drawer opens to, as a fraction of the tab's working area. In-app Settings edit: applies to new tabs (a hand resize owns the running ratio).
right-drawer-fractiondefault0.30clamped to 0.1…0.9Initial size a drawer opens to, as a fraction of the tab's working area. In-app Settings edit: applies to new tabs (a hand resize owns the running ratio).
drawer-resize-stepdefault40clamped to 4…400One ⌘⌃arrow nudge when resizing a focused drawer (px), and the ceiling a drawer may grow to (fraction of the working area; the canvas keeps the rest). In-app Settings edit: both apply live.
max-drawer-fractiondefault0.7clamped to 0.3…0.95One ⌘⌃arrow nudge when resizing a focused drawer (px), and the ceiling a drawer may grow to (fraction of the working area; the canvas keeps the rest). In-app Settings edit: both apply live.
Behavior
reduce-motiondefaultsystemsystem | on | offAnimation policy. `system` follows the macOS "Reduce motion" accessibility setting; `on`/`off` force it regardless. In-app Settings edit: applies live.
shelldefault/bin/zshShell for new panes. Unset → $SHELL, then /bin/zsh. When set, it launches login+interactive by default; override the flags with shell-args. In-app Settings edit: applies to new tabs.
shell-argsdefault-l -iwhitespace-separated argvShell for new panes. Unset → $SHELL, then /bin/zsh. When set, it launches login+interactive by default; override the flags with shell-args. In-app Settings edit: applies to new tabs.
tab-inherit-cwddefaultfalsetrue | falseWhere a new tab (⌘T) or window (⌘N) starts. Off = your home directory, so a new tab is a clean slate. On = the focused pane's working directory. Panes (⌘D / ⌘⇧D), drawers, tool floats and ⌘P workspace tabs inherit either way. In-app Settings edit: applies to the next tab or window you open.
editordefaultnvimThe two commands the "Editor + AI + Shell" workspace preset launches, in its main pane and its right drawer. Unset → nvim and claude. Any command works: these are what the preset runs, not a declaration of what you use. In-app Settings edit: applies to the next workspace you build from the preset.
aidefaultclaudeThe two commands the "Editor + AI + Shell" workspace preset launches, in its main pane and its right drawer. Unset → nvim and claude. Any command works: these are what the preset runs, not a declaration of what you use. In-app Settings edit: applies to the next workspace you build from the preset.
Notifications
agent-notificationsdefaulttruetrue | falsePost a macOS banner when an agent needs attention and ZenTerm is not the front app. macOS decides whether the banner appears at all, under System Settings > Notifications; this is the switch on ZenTerm's side. In-app Settings edit: applies live.
attention-toastdefaultstickysticky | autoThe in-app card for a background tab that needs you. Sticky waits to be answered; auto clears itself after `toast-duration`. Either way the tab's colored number stays until you visit the tab or press Dismiss. In-app Settings edit: applies to the next notification.
completion-toastdefaultstickysticky | autoThe same, for the card a long command posts when it finishes in a background tab. In-app Settings edit: applies to the next notification.
toast-durationdefault41-60Seconds a card that clears itself stays up. Covers every notice that clears itself, not only the two above. In-app Settings edit: applies to the next notice.
Updates
automatic-update-checksdefaulttruetrue | falseCheck for a new release in the background. On by default; turn it off to check only when you run the "Check for Updates" command. You choose when to install. In-app Settings edit: applies live.
Diagnostics
debugdefaultfalsetrue | falseTee verbose diagnostics to the log file. Off by default: the log then holds warnings, errors, and a trail of key actions (panes, tabs, drawers). Turn it on to add deep traces for a bug report. The log is at ~/Library/Logs/ZenTerm/zen-term.log; ZENTERM_LOG_VERBOSE=1 is the env equivalent. To attach it to a bug report, use Help → Export Diagnostics: it saves a zip of the log files plus your system info (app version, macOS version, arch).
Tool floats
A tool float is a command overlay on a chord. There are no built-in floats. Define your own with repeatable float = lines, each a set of whitespace-separated field:value pairs.
titlerequiredname in the palette, Settings, and the toolbar tooltip; must be unique, and must hold a letter or number
orderoptionalposition in the toolbar, palette, and Settings (low to high) (default: the order the `float =` lines appear in here). Settings writes these for you when you reorder with ⌥↑/⌥↓
commandrequiredruns as `$SHELL -l -i -c <command>` at the focused cwd
diroptionalpinned working directory, ~ expanded (default: focused pane's cwd) for tools that aren't about the directory you're in (a music player) or that mean a specific one (a notes scratchpad)
keyrequiredtoggle chord, e.g. cmd+shift+g (see "Chord syntax" below)
gitoptionaltrue = only open inside a git repo (default false)
persistoptionalprocess lifetime (default none) none = terminate on dismiss, fresh every open dir = keep alive per directory (repo root if in a repo, else the cwd); reopening elsewhere discards it and respawns. With a pinned `dir:` the anchor is constant, so the instance never re-anchors window = keep alive for the window, anchored where it first opened and never re-anchored, for tools that aren't about the directory you're in at all a float is always window-level: one live instance shared by every tab in the window, and its card stays up across a tab switch. the instance dies when the tool quits on its own (e.g. `q`); the next open is cold, not resumed
toolbaroptionalfalse = no toolbar button; the shortcut (default true) and palette entry stay live
iconoptionalSF Symbol name, or a bundled brand mark (default square.on.square) (github, git, docker, claude, htop and more: the full set is the icon picker in Settings → Tools)
widthoptionalwidth as a fraction of the tab (default 0.85)
heightoptionalheight as a fraction of the tab (default 0.85)
Sample config
The full annotated file. Every line is commented out, so copying it changes nothing until you uncomment a key.
# zen-term: general config reference
#
# Copy to ~/.config/zen-term/config (or $XDG_CONFIG_HOME/zen-term/config) and
# uncomment what you want to change. Every value below is the built-in default,
# so an empty or absent file behaves exactly like this.
#
# Format: ghostty-flavored `key = value`. Lines starting with `#` and blank
# lines are ignored. Unknown keys are ignored. A malformed value falls back to
# the default; an out-of-range number is clamped. Nothing here can crash the app:
# a bad value is logged, skipped, and shown in the app (inline on its Settings
# row, or in a notice when you reload). Config is read once at launch. Restart to
# apply hand-edits. Changes made in the in-app Settings card (⌘,) apply live.
# ─── Theme ───────────────────────────────────────────────────────────────────
# Select a named theme from ~/.config/zen-term/themes/. Keep a library there and
# switch with this one line. Each file is ghostty-format (drop in any ghostty
# theme unchanged); it recolors both the terminal and the chrome. Unset → a
# legacy single ~/.config/zen-term/theme file if present, else Rosé Pine Zen.
# A name that doesn't exist warns and falls back to that default. See
# docs/config/themes/ for an example.
#
# The app also ships a bundled theme catalog, selectable in Settings →
# Appearance → Theme without hand-editing this file. A name resolves a user
# themes/<name> file first, else a bundled theme. The picker writes this same
# key; restart the app to apply the change.
# theme = rose-pine-zen
# The chrome's accent: the color for focus, active state, and confirm. It paints
# the focused pane's glow and border, focus rings, the active tab's underline, a
# tool's activity dot, primary buttons, and the progress bar. Pick any slot of the
# theme's palette and the whole chrome follows; the terminal is untouched, so a
# program printing in that color is unaffected.
#
# The names are the usual ANSI hues, so what you get is whatever your theme put in
# that slot: on Rosé Pine Zen, magenta is iris. Settings → Appearance → Accent
# color shows each one's swatch and hex. Unset → blue (palette 4).
#
# black red green yellow blue magenta cyan white
# and a bright- variant of each (bright-red, bright-cyan, …)
# accent-color = blue
# ─── Terminal appearance ─────────────────────────────────────────────────────
# Monospaced font for the terminal surfaces. (Colors live in a theme file, not
# here. See the Theme section above.)
# font-family = JetBrainsMono Nerd Font Mono
# font-size = 14 # points; clamped to 6…32. ⌘+ / ⌘- step this for the
# session (⌘0 returns here); the file is unchanged.
# Fake-bold every glyph by dilating its coverage. Off matches stock ghostty. On
# is heavier than it sounds: the thickening strength is maxed out, so on a Retina
# panel it reads as a permanent bold rather than a nudge.
# In-app Settings edit: applies live.
# font-thicken = false # true | false
# Cursor shape and blink.
# cursor-style = block # block | bar | underline
# cursor-style-blink = true # true | false
# Cursor thickness in px for the bar/underline styles (block fills the cell, so
# it ignores this). ghostty's base is a nearly invisible 1px, so the default is 2.
# cursor-thickness = 2 # clamped to 1…12
# Treat the ⌥ (Option) key as Alt/Meta: enables ⌥f/⌥b word-nav in readline and
# Meta chords in vim/emacs, at the cost of macOS accent composing. true = Meta.
# macos-option-as-alt = true # true | false
# Precise-scroll (trackpad) feel multiplier. 1 = raw, higher = faster.
# scroll-multiplier = 1.5 # clamped to 0.1…10
# A bundled GLSL cursor shader drawn over the terminal surface. Bundled, vetted
# options only, one at a time: `cursor_warp` is a pronounced Neovide-style smear,
# `cursor_tail` a lighter comet. Unset = none. Also in Settings → Terminal → Cursor
# shader, which applies live. These are cursor effects, so they show on cursor
# moves and read most in editors like neovim; they do not affect scrolling.
#
# A shader redraws the whole terminal surface every frame while a pane is focused,
# so what it costs scales with the pixels on screen. On a 5K display that measured
# as most of the GPU. Drawing stops when ZenTerm is not frontmost, and when the
# window is covered, so a shader costs nothing in the background.
# cursor-shader = cursor_warp # cursor_warp | cursor_tail
# Translucency of the terminal background. 1 = solid, 0 = your text over the
# window blur with the desktop behind it. The inner padding around a terminal
# follows the same value, in a pane and in a tool float, so each reads as one
# surface. A float frosts the panes it covers, the way a pane frosts the desktop.
# Cards stay solid: toasts, Settings, the palette.
# Set `backdrop-alpha` below to dial the window shell.
# In-app Settings edit: applies live.
# background-alpha = 1 # clamped to 0…1
# ─── Chrome ──────────────────────────────────────────────────────────────────
# Show the standard macOS window buttons (close, minimize, zoom) and reserve the
# header space that clears them. Off = a chromeless top; close/minimize still
# work via ⌘W / ⌘M and the menu, and the window drags by its background.
# In-app Settings edit: applies live.
# window-chrome = true # true | false
# Hide any of the footer toolbar's built-in buttons (bottom-right of the tab bar).
# Comma-separated, any order. Hiding is visual only: every hidden button's
# shortcut and palette entry stay live. Buttons, in toolbar order:
# new-tab, split-h, split-v, bottom-drawer, right-drawer, scratch, focus-mode,
# command-palette
# In-app Settings edit: applies live.
# hide-toolbar-buttons = # e.g. split-h,split-v
# Opacity of the on-brand tint over the behind-window blur. 1 = solid shell,
# 0 = raw system blur showing your desktop through the whole window.
#
# Below 1, part of what you see through the shell is your desktop, and the chrome
# on top of it (the footer icons, the tab titles) reads against that. A strongly
# contrasting wallpaper costs some of their legibility, which is the trade a lower
# value buys. Set 1 for a solid shell if you would rather keep the contrast.
# In-app Settings edit: applies live.
# backdrop-alpha = 0.82 # clamped to 0…1
# Inset from the window edge to the tiled panes, all four sides (px).
# In-app Settings edit: applies live.
# window-gutter = 8 # clamped to 0…64
# Gap between tiled panes, and between the canvas and a drawer (px).
# In-app Settings edit: applies live.
# pane-gap = 8 # clamped to 0…64
# Initial size a drawer opens to, as a fraction of the tab's working area.
# In-app Settings edit: applies to new tabs (a hand resize owns the running ratio).
# bottom-drawer-fraction = 0.28 # clamped to 0.1…0.9
# right-drawer-fraction = 0.30 # clamped to 0.1…0.9
# One ⌘⌃arrow nudge when resizing a focused drawer (px), and the ceiling a
# drawer may grow to (fraction of the working area; the canvas keeps the rest).
# In-app Settings edit: both apply live.
# drawer-resize-step = 40 # clamped to 4…400
# max-drawer-fraction = 0.7 # clamped to 0.3…0.95
# ─── Behavior ────────────────────────────────────────────────────────────────
# Animation policy. `system` follows the macOS "Reduce motion" accessibility
# setting; `on`/`off` force it regardless.
# In-app Settings edit: applies live.
# reduce-motion = system # system | on | off
# Shell for new panes. Unset → $SHELL, then /bin/zsh. When set, it launches
# login+interactive by default; override the flags with shell-args.
# In-app Settings edit: applies to new tabs.
# shell = /bin/zsh
# shell-args = -l -i # whitespace-separated argv
# Where a new tab (⌘T) or window (⌘N) starts. Off = your home directory, so a new
# tab is a clean slate. On = the focused pane's working directory. Panes (⌘D / ⌘⇧D),
# drawers, tool floats and ⌘P workspace tabs inherit either way.
# In-app Settings edit: applies to the next tab or window you open.
# tab-inherit-cwd = false # true | false
# The two commands the "Editor + AI + Shell" workspace preset launches, in its
# main pane and its right drawer. Unset → nvim and claude. Any command works:
# these are what the preset runs, not a declaration of what you use.
# In-app Settings edit: applies to the next workspace you build from the preset.
# editor = nvim
# ai = claude
# ─── Notifications ───────────────────────────────────────────────────────────
# Post a macOS banner when an agent needs attention and ZenTerm is not the front
# app. macOS decides whether the banner appears at all, under System Settings >
# Notifications; this is the switch on ZenTerm's side.
# In-app Settings edit: applies live.
# agent-notifications = true # true | false
# The in-app card for a background tab that needs you. Sticky waits to be
# answered; auto clears itself after `toast-duration`. Either way the tab's
# colored number stays until you visit the tab or press Dismiss.
# In-app Settings edit: applies to the next notification.
# attention-toast = sticky # sticky | auto
# The same, for the card a long command posts when it finishes in a background
# tab.
# In-app Settings edit: applies to the next notification.
# completion-toast = sticky # sticky | auto
# Seconds a card that clears itself stays up. Covers every notice that clears
# itself, not only the two above.
# In-app Settings edit: applies to the next notice.
# toast-duration = 4 # 1-60
# ─── Updates ─────────────────────────────────────────────────────────────────
# Check for a new release in the background. On by default; turn it off to check
# only when you run the "Check for Updates" command. You choose when to install.
# In-app Settings edit: applies live.
# automatic-update-checks = true # true | false
# ─── Diagnostics ─────────────────────────────────────────────────────────────
# Tee verbose diagnostics to the log file. Off by default: the log then holds
# warnings, errors, and a trail of key actions (panes, tabs, drawers). Turn it on
# to add deep traces for a bug report. The log is at
# ~/Library/Logs/ZenTerm/zen-term.log; ZENTERM_LOG_VERBOSE=1 is the env equivalent.
# To attach it to a bug report, use Help → Export Diagnostics: it saves a zip of the
# log files plus your system info (app version, macOS version, arch).
# debug = false # true | false
# ─── Tool floats ─────────────────────────────────────────────────────────────
#
# Command overlays (toolbar button + command-palette entry + keybinding) whose
# process lifetime is set by `persist:` (default `none` = ephemeral, fresh every
# open). Define your own with `float = ` lines, repeating the key for as many as
# you like. A float's `key:` is the single source of truth for both its chord and
# its palette glyph.
#
# One float ships built in: Scratch, on cmd+;, a blank shell over the tab that keeps
# running when you dismiss it and dies when you type `exit`. Each tab gets its own,
# the way each tab gets its own drawers, and closing a tab closes the shell in it.
# It has no line here and no Settings > Tools row. Rebind it under Keybinds below, or in
# Settings > Shortcuts. `scratch` is a reserved name: a `float =` line whose title
# lands on it is refused, and the app tells you which line it was.
#
# You don't have to write one by hand: Settings > Tools has a form, and so does
# the "New Tool Float" command in the palette. Either one writes the line here.
#
# A float has no id to author: its `title:` is its name, and Settings derives the
# rest. (The id a `toggle_float:` keybind needs is just the title lowercased, with
# each run of anything that isn't a letter or number turned into a single dash:
# "Open GitDash" -> open-gitdash.)
#
# Fields (whitespace-separated `field:value`; quote values with spaces):
# title (required) name in the palette, Settings, and the toolbar tooltip;
# must be unique, and must hold a letter or number
# order (optional) position in the toolbar, palette, and Settings (low to high)
# (default: the order the `float =` lines appear in here).
# Settings writes these for you when you reorder with ⌥↑/⌥↓
# command (required) runs as `$SHELL -l -i -c <command>` at the focused cwd
# dir (optional) pinned working directory, ~ expanded (default: focused pane's cwd)
# for tools that aren't about the directory you're in (a music
# player) or that mean a specific one (a notes scratchpad)
# key (required) toggle chord, e.g. cmd+shift+g (see "Chord syntax" below)
# git (optional) true = only open inside a git repo (default false)
# persist (optional) process lifetime (default none)
# none = terminate on dismiss, fresh every open
# dir = keep alive per directory (repo root if in a repo, else
# the cwd); reopening elsewhere discards it and respawns.
# With a pinned `dir:` the anchor is constant, so the
# instance never re-anchors
# window = keep alive for the window, anchored where it first
# opened and never re-anchored, for tools that aren't
# about the directory you're in at all
# a float is always window-level: one live instance shared by
# every tab in the window, and its card stays up across a tab
# switch. the instance dies when the tool quits on its own
# (e.g. `q`); the next open is cold, not resumed
# toolbar (optional) false = no toolbar button; the shortcut (default true)
# and palette entry stay live
# icon (optional) SF Symbol name, or a bundled brand mark (default square.on.square)
# (github, git, docker, claude, htop and more:
# the full set is the icon picker in
# Settings → Tools)
# width (optional) width as a fraction of the tab (default 0.85)
# height (optional) height as a fraction of the tab (default 0.85)
#
# Example: the GitHub dashboard TUI, on a chord you pick:
# float = order:1 title:"Open GitDash" command:"gd" key:cmd+ctrl+g git:false icon:github
#
# Example: a dev server with spaces in the command:
# float = order:2 title:"Dev server" command:"npm run dev" key:cmd+shift+e
#
# Example: lazygit kept warm per repo (first open is cold; reopens are instant).
# float = order:3 title:"Open Lazygit" command:"lazygit" key:cmd+shift+l git:true persist:dir icon:git height:0.78
#
# Example: a system monitor kept alive for the window, reachable from every tab:
# float = order:4 title:BTop command:"btop" key:cmd+shift+b persist:window icon:gauge
#
# Example: zen-review, the standalone review tool, on ⌘G. It keeps a session per repo,
# so `persist:dir` picks the reading back up where you left it.
# float = order:5 title:Review command:"zen-review" key:cmd+g git:true persist:dir icon:checklist
# ─── Keybinds ────────────────────────────────────────────────────────────────
#
# Remap any built-in action with `keybind = <action>=<chord>`. The action reads
# first. Your lines override the defaults; if two lines claim the same chord, the
# last one wins (and a warning is logged). An unknown action is skipped with a warning.
#
# Write `none` in place of a chord to take an action's shortcut away entirely:
#
# keybind = clear_screen=none
#
# The chord then reaches the program in your pane like any other. `unbind` works
# too, since that's ghostty's word. (ghostty writes the trigger first, so its own
# `cmd+g=unbind` still reads here as an unknown action.)
#
# In Settings, Delete on a shortcut does the same thing, and the capture popover has
# a reset icon beside the input that puts the default back.
#
# When a line here takes a chord off one of the defaults, the action that lost it
# gets a notice of its own at launch, with two answers. Accept writes the `=none`
# line for you. Revert drops the line that took the chord, so both actions go back
# to their defaults. Closing the notice with its × changes nothing, and it returns
# every launch until you answer it or fix the file yourself.
#
# A float is Accept only. Its chord is the `key:` on its own `float =` line and
# `key:` is required, so there is nothing to back the chord out to.
#
# A chord the menu bar owns is refused, and so is a tool float's `key:` on one.
# ZenTerm resolves shortcuts before macOS matches menu items, so a keybind on ⌘Q
# would win and Quit would stop working with the menu still showing ⌘Q beside it.
# The refused line is reported on reload and the action keeps its default.
#
# Chord syntax: `+`-joined modifiers then one key, e.g. cmd+shift+g.
# modifiers: cmd (command/super), shift, opt (option/alt), ctrl (control)
# key: a single character (letter, digit, or symbol like - \ [ ])
# or one of: up down left right enter tab home end page_up page_down
# (ghostty's arrow_up and friends work too)
#
# Write the key as it's printed *unshifted*, and add `shift` yourself: cmd+shift+-
# rather than cmd+shift+_. Both spellings work: a shifted glyph folds onto its base
# key (_ → shift+-, | → shift+\, plus → shift+=). But the unshifted form is what the
# app displays and writes back, so it's the one to reach for.
#
# Actions (the full set):
# split_horizontal split_vertical close_pane
# nav_left nav_right nav_up nav_down prev_pane next_pane
# resize_left resize_right resize_up resize_down
# new_tab new_window prev_tab next_tab select_tab_1 … select_tab_9
# move_tab_left move_tab_right
# rename_tab (no default binding; double-click a tab, or use the palette)
# toggle_bottom_drawer toggle_right_drawer toggle_focus_mode fill_screen
# toggle_scroll_mode toggle_search
# scroll_to_top scroll_to_bottom scroll_page_up scroll_page_down
# jump_to_previous_prompt jump_to_next_prompt scroll_to_selection
# search_selection
# search_next search_previous (no default; `n` and `N` step a live search)
# clear_screen paste_selection
# dismiss_toast dismiss_all_toasts
# select_all (no default; Edit > Select All holds ⌘A)
# write_screen_file write_screen_file_copy write_screen_file_open
# toggle_workspace_picker toggle_command_palette
# open_settings reload_config
# increase_font_size decrease_font_size reset_font_size
# check_for_updates (no default binding; bind a chord to reach it by keyboard)
# report_issue (no default binding; also in the command palette and Help menu)
# new_tool_float (no default binding; opens the form that adds one, also in the palette)
# toggle_float:<id> (bind a second chord to one of your floats; `none` doesn't
# apply here, since a float's chord is its own `key:` field.
# the exception is the built-in `toggle_float:scratch`,
# which has no `key:` field: it rebinds and unbinds like
# any other action)
#
# Defaults (uncomment to change; these are what ships):
# keybind = split_vertical=cmd+d # a new pane to the right
# keybind = split_horizontal=cmd+shift+d # a new pane below
# keybind = close_pane=cmd+w
# keybind = nav_left=cmd+opt+left
# keybind = nav_down=cmd+opt+down
# keybind = nav_up=cmd+opt+up
# keybind = nav_right=cmd+opt+right
# keybind = prev_pane=cmd+shift+[ # step through the panes in order, wrapping
# keybind = next_pane=cmd+shift+]
# keybind = resize_left=cmd+ctrl+left
# keybind = resize_down=cmd+ctrl+down
# keybind = resize_up=cmd+ctrl+up
# keybind = resize_right=cmd+ctrl+right
# keybind = new_tab=cmd+t
# keybind = new_window=cmd+n
# keybind = prev_tab=cmd+[
# keybind = next_tab=cmd+]
# keybind = move_tab_left=cmd+ctrl+[ # shift the active tab one slot along the bar
# keybind = move_tab_right=cmd+ctrl+]
# keybind = select_tab_1=cmd+1 # … through select_tab_9=cmd+9
# keybind = toggle_bottom_drawer=cmd+b
# keybind = toggle_right_drawer=cmd+\
# keybind = toggle_float:scratch=cmd+; # the built-in scratch shell
# keybind = toggle_focus_mode=cmd+shift+enter # one pane/drawer fills the window
# keybind = fill_screen=cmd+enter # the window fills the desktop
# keybind = toggle_scroll_mode=cmd+shift+s # vim keys move the focused pane's scrollback
# keybind = toggle_search=cmd+f # find in the focused pane's scrollback
# keybind = scroll_to_top=cmd+home
# keybind = scroll_to_bottom=cmd+end
# keybind = scroll_page_up=cmd+page_up
# keybind = scroll_page_down=cmd+page_down
# keybind = search_selection=cmd+e # find what you have selected
# keybind = paste_selection=cmd+shift+v # paste what you have selected
# keybind = clear_screen=cmd+k # clears the scrollback with it
# keybind = scroll_to_selection=cmd+j
# keybind = write_screen_file=cmd+shift+j # writes it, then types the path
# keybind = write_screen_file_copy=cmd+shift+ctrl+j # …copies the path instead
# keybind = write_screen_file_open=cmd+shift+opt+j # …opens the file instead
# keybind = jump_to_previous_prompt=cmd+shift+up
# keybind = jump_to_next_prompt=cmd+shift+down
# keybind = dismiss_toast=cmd+shift+n # the oldest notice; press again to walk down
# keybind = dismiss_all_toasts=cmd+shift+opt+n
# keybind = toggle_command_palette=cmd+shift+p
# keybind = toggle_workspace_picker=cmd+p
# keybind = open_settings=cmd+,
# keybind = reload_config=cmd+shift+,
# keybind = increase_font_size=cmd+= # ⌘+ (cmd+shift+=) does this too
# keybind = decrease_font_size=cmd+-
# keybind = reset_font_size=cmd+0 # back to font-size above
#
# ─── Coming from Ghostty ─────────────────────────────────────────────────────
#
# The table above is Ghostty's, chord for chord, with three exceptions.
#
# ⌘[ and ⌘] are tabs here rather than panes, matching Safari and the rest of the
# Mac. Ghostty's ⌃⇥ tab cycling isn't bound either, because ⌘] already is:
# `keybind = next_tab=ctrl+tab` if you want it back.
#
# ⌃⌘F does nothing. Ghostty spells fullscreen that way as well as ⌘⏎, and ⌃⌘F is
# macOS's own native-fullscreen chord, which this isn't: Fill Screen maximizes the
# window without taking a space.
#
# Pane cycling takes ⌘⇧[ and ⌘⇧], which is Ghostty's tab cycling. With tabs on the
# unshifted pair here, the whole bracket family is inverted: Ghostty puts panes on the
# bare pair and tabs on the shifted one, and ZenTerm does the opposite.
#
# Every action holds exactly one shortcut, so there is one place to look when you
# want to change something, and rebinding it moves the whole action.
#
# Every other chord reaches the program in your pane, with the exceptions below.
# Rebind an action away and its old chord goes there too, so ⌘⇧S in vim is vim's ⌘⇧S
# once you have moved toggle_scroll_mode off it. `=none` is the same thing without
# having to park the action somewhere first.
#
# A handful stay with the terminal on purpose, because they turn into bytes for the
# program rather than doing anything to the app:
# ⌘← ⌘→ jump to the start and end of the line
# ⌘⌫ delete to the start of the line
# ⌥← ⌥→ move a word at a time
# ⇧ and an arrow, Home, End, Page Up or Page Down, extends a selection
#
# Three actions ship with no shortcut:
# check_for_updates runs a manual update check
# report_issue opens the Report an Issue composer
# new_tool_float opens the form that adds a tool float
#
# The first two are menu-bar errands; the third is something you do once in a while, so it
# spends no chord by default. All three are in the command palette, and take a chord here
# if you want one.
#
# Chords ZenTerm does not ship, if you want them:
# keybind = nav_left=cmd+h # ⌘HJKL nav, with the other three
# keybind = resize_left=cmd+shift+h # ⌘⇧HJKL resize, likewise
# keybind = split_vertical=cmd+shift+\
# keybind = split_horizontal=cmd+shift+-
# keybind = fill_screen=cmd+shift+f
# keybind = toggle_search=cmd+/
# keybind = reload_config=cmd+opt+r
#
# Control-key pane nav, the tmux and vim habit, needs four lines and nothing else.
# It costs you ⌃L clear-screen and ⌃K kill-line at the shell prompt, which is why
# it isn't the default:
# keybind = nav_left=ctrl+h
# keybind = nav_down=ctrl+j
# keybind = nav_up=ctrl+k
# keybind = nav_right=ctrl+l
#
# ─── Scroll mode ─────────────────────────────────────────────────────────────
#
# `toggle_scroll_mode` (⌘⇧S) reads back through the focused pane's scrollback with
# the keyboard. While it's up the pane header says SCROLL and how far below the
# viewport the buffer runs, a band marks the row you're on, and nothing reaches the
# shell:
#
# j / k (or ↑ ↓) move the cursor a row { / } move by paragraph
# h / l (or ← →) move a column 0 / ^ / $ ends of the row
# w / b / e move by word gg / G top / bottom
# W / B / E move by WORD H / M / L top / middle / last row
# f / F / t / T find a character on the row ; / , that find again, or back
# ctrl+d / ctrl+u half a page v / V select, by cell or row
# ctrl+f / ctrl+b a full page (space = ctrl+f) y / yy copy the selection, or rows
# * find this word in the buffer esc, q, i leave
#
# A page moves the cursor, not just the view: it walks half a screen down the buffer
# and the view follows, so the band sits in the middle while the text runs past it.
# At the end of the buffer the view stops and the band carries on to the last line.
#
# Every motion takes a count, so `12j` moves twelve rows and `3yy` copies three.
# `0` is the start of the row on its own and a digit once you are typing a count,
# which is vim's rule: without it `10j` would jump to column 0 and step once.
#
# With text selected, the mode opens on the start of your selection. With nothing
# selected it opens on the last line with anything written on it.
#
# Your own chords still work inside it, so ⌘T and pane nav are never swallowed.
# `{` and `}` are vim's paragraph motion: they move the cursor to the next blank
# line, which in a terminal is what separates one command's output from the next.
# The mode ends on its own when focus moves, the pane closes, you change tab, or a
# tool float or palette opens.
#
# With a tool float open, ⌘⇧S reads the float's buffer instead of the pane behind it.
# The card wears the header while the mode is up, and closing the card ends the mode.
#
# `v` starts a selection at the cursor and `V` takes whole rows; motions grow it, `y`
# copies it and pulses what it took, and Esc hands it back without leaving the mode.
# A selection follows its text: scrolling grows it by the rows that arrive, and a resize
# re-finds it where the rewrap put it. It goes when the row you started on leaves the
# screen, because the terminal core can only read text you can see.
#
# Some chords move the viewport without the mode, for when you want a look rather
# than a read: ⌘Home and ⌘End go to the ends of the buffer, ⌘Page Up and ⌘Page Down
# move a screen at a time, and ⌘⇧↑ and ⌘⇧↓ walk between the prompts your shell
# marked. All of them repeat if you hold them, and they work on the focused drawer.
#
# ⌘⇧↑ is not scroll mode's `{`, and both are worth having. `{` moves the cursor to a
# blank line on screen; ⌘⇧↑ moves the buffer, so it reaches a prompt that scrolled
# off the top and does nothing when your whole session fits on one screen. Prompt
# marks come from shell integration, so a shell that sets none leaves it with nothing
# to find.
#
# The terminal binds these on bare ⌘↑ and ⌘↓ as well. ZenTerm does not, because macOS
# claims that pair on most Macs and the keypress never arrives. Bind them here if
# yours is free.
#
# ─── Find ────────────────────────────────────────────────────────────────────
#
# `toggle_search` (⌘F) opens a find bar along the bottom of the focused pane and
# searches its whole scrollback, not just what's on screen. Every match is
# highlighted as you type and the bar counts them. If what you typed is only in
# history, the pane scrolls to it so you can see it while you type; if a match is
# already on screen, nothing moves. With a tool float open the bar opens in the card
# and searches the float's scrollback.
#
# ⌘F opens on whatever you have selected, so searching a word you can see never
# means retyping it. Either kind counts: a mouse selection, or a `v` selection made
# in scroll mode. With nothing selected the bar opens empty. `search_selection` (⌘E)
# is the same thing except that with nothing selected it does nothing at all.
#
# Stepping matches is `n` and `N` while the search holds the keyboard, not a chord,
# so `search_next` and `search_previous` ship with no shortcut. Give them one here if
# you would rather step on a chord: `keybind = search_next=cmd+opt+g` and its shifted
# pair. The old `find_next` and `find_previous` still parse.
# With no bar up there is no search to step and the chord does nothing.
#
# Enter hands the keys back to the pane and puts you in scroll mode on the match,
# where `n` and `N` step forward and back (Enter and shift+Enter do the same), and
# `v` / `V` / `y` select and copy what you found. Esc closes the bar and leaves
# scroll mode with it, unless you were already in scroll mode when you opened it.
#
# The count reads in the order you see on screen: match 1 is the one nearest the top
# of your scrollback. Stepping does not wrap, so `n` on the oldest match and `N` on
# the newest both stay where they are.
#
# The find bar and a live prompt never sit on screen together. So leaving scroll mode
# any other way closes the bar too: `q`, `i`, and ⌘⇧S all end the search and give the
# prompt back every key, `n` and `N` included.
#
# If the search scrolled you back through the buffer, closing it returns you to
# where you were when you opened it, and so does typing past the last match. That is
# the bottom when you searched from a live prompt, and the place you were reading if
# you had already scrolled. You are never left parked on something that no longer
# matches what you typed.
#
# The match colors follow your theme. A theme file can set them itself with
# `search-background`, `search-foreground`, `search-selected-background` and
# `search-selected-foreground`; unset, they're derived from the chrome accent.
#
# The cursor lands on the match by reading the screen back, because the terminal
# core reports which match is selected but never where it is. With several matches
# on one screen it can land on the wrong one. The selected match is highlighted
# differently, so you'll see it, and one `j` or `k` fixes it.
#
# ─── The screen ──────────────────────────────────────────────────────────────
#
# ⌘A selects the whole buffer, ready for ⌘C. It is the terminal's own selection, the
# same one a mouse drag makes, so `scroll_to_selection` (⌘J) brings the viewport back
# to where it starts.
#
# ⌘A comes from Edit > Select All, not from a keybind. A menu shortcut reaches a text
# field as well as a pane; a keybind is resolved before the field ever sees the key, so
# binding ⌘A here would take it away from every field in the app.
#
# Two things follow, and they apply to ⌘A the way they already do to ⌘C and ⌘V:
#
# * Any `keybind` or float `key:` landing on ⌘A is refused, not just `select_all`.
# A line that worked before, say `keybind = clear_screen=cmd+a`, is dropped with a
# notice naming the menu item.
# * `select_all=none` does not hand ⌘A back to the program in the pane. Unbinding is
# the keymap's, and this chord is the menu's.
#
# `select_all` is still an action: bind it to another chord and it works over a pane
# like any other.
#
# `paste_selection` (⌘⇧V) types what you have selected back into the pane. Either
# kind counts, a mouse selection or a `v` selection from scroll mode, and with
# nothing selected it does nothing. ⌘V is still what pastes the clipboard.
#
# `clear_screen` (⌘K) wipes the screen and the scrollback with it, the way `clear`
# does. It leaves a full-screen program alone: vim and its kind draw on a second screen
# that has no scrollback to clear. Over an open tool float it clears the card.
#
# `write_screen_file` (⌘⇧J) writes what is on screen to a file and types the path at
# your prompt, so the next command has something to point at. Reach for it when a wall
# of output is easier to grep than to read.
#
# The same write, two other endings: `write_screen_file_copy` (⌘⇧⌃J) puts the path on
# the clipboard, and `write_screen_file_open` (⌘⇧⌥J) opens the file. The file is
# written either way, so the three differ only in where the path goes.
Keybinds have their own reference.