A Practical Ghostty-Based Terminal Stack
A practical macOS terminal stack built around Ghostty, Maple Mono NF CN, zinit, Starship, zoxide, fzf, Yazi, and modern CLI tools, focused on CJK-friendly monospace rendering, startup performance, readability, history preservation, and reproducible setup.
A Practical Ghostty-Based Terminal Stack
A good terminal setup should not feel heavy. It should start quickly, render text and icons reliably, preserve command history, make navigation fast, and improve the readability of everyday commands.
The stack I settled on is:
Ghostty + Maple Mono NF CN + zinit + Starship + zoxide + fast-syntax-highlighting + zsh-autosuggestions + fzf + Yazi + eza/ripgrep/fd/bat/btop/duf/httpie.
The goal is not to build a large shell framework. The goal is to replace a traditional Terminal.app + Oh My Zsh + directly sourced plugins setup with a clearer, faster, reproducible terminal environment.
The Stack At A Glance
flowchart LR
A[Ghostty<br/>GPU accelerated terminal] --> B[Maple Mono NF CN<br/>CJK monospace and Nerd Font icons]
B --> C[zsh startup]
C --> D[zinit<br/>lazy plugin loading]
D --> E[zsh-autosuggestions<br/>history-based suggestions]
D --> F[fast-syntax-highlighting<br/>command highlighting]
C --> G[zoxide<br/>smart cd]
C --> H[Starship<br/>cross-shell prompt]
C --> I[modern CLI aliases<br/>eza rg fd bat btop duf]
C --> J[fzf<br/>key bindings and completion]
C --> K[Yazi<br/>terminal file manager]
The key idea is separation of responsibility. The terminal emulator handles rendering. The font handles icons. zsh stays small. zinit loads interactive plugins lazily. Starship owns the prompt. zoxide owns navigation. Modern CLI tools improve the commands used every day.
From The Old Setup To The New Setup
A common old setup looks like this:
flowchart TB
subgraph Old[Old setup]
O1[Terminal.app] --> O2[Oh My Zsh]
O2 --> O3[theme]
O2 --> O4[directly sourced autosuggestions]
O2 --> O5[directly sourced syntax highlighting]
end
subgraph New[New setup]
N1[Ghostty] --> N2[lean zsh]
N2 --> N3[zinit lazy plugins]
N2 --> N4[Starship prompt]
N2 --> N5[zoxide + fzf]
N2 --> N6[modern CLI tools]
end
The migration does not need to delete Oh My Zsh or old plugins. A safer approach is to comment out the old loading lines so they no longer affect shell startup. The files remain available for rollback.
Tools And Plugins
| Component | Role | Core capability | Why it is in the stack |
|---|---|---|---|
| Ghostty | Terminal emulator | GPU/Metal rendering, native macOS behavior, file-based config | More modern and reproducible than the stock Terminal setup |
| Maple Mono NF CN | Font | Consistent Latin/CJK monospace, Nerd Font icons, Powerline symbols | One family covers Latin, CJK, and icons without font-fallback inconsistencies |
| zinit | zsh plugin manager | Lazy loading and plugin management | Replaces heavier framework-style loading |
| zsh-autosuggestions | Input suggestions | Grey suggestions from shell history | Keeps the familiar history-based workflow |
| fast-syntax-highlighting | Syntax highlighting | Highlights commands, arguments, and paths | Fast enough for interactive use |
| Starship | Prompt | Git status, directory, language versions, command duration | Cross-shell, compact, and easy to configure |
| zoxide | Navigation | Frequency-aware directory jumping | Preserves the cd habit while adding smart jumps |
| fzf | Fuzzy finder | Ctrl-R history search, fuzzy completion, file selection | Speeds up high-frequency interactive tasks |
| Yazi | Terminal file manager | Three-pane browsing, previews, file operations, return-to-directory workflow | Bridges Finder-like browsing and shell navigation inside the terminal |
| eza | ls replacement | Icons, Git status, tree view | Restores and improves ll/la/l/lt shortcuts |
| ripgrep | grep replacement | Fast full-text search | Better default for code and text search |
| fd | find replacement | Simple syntax and sensible ignores | Easier day-to-day file discovery |
| bat | cat replacement | Syntax highlighting and paging | More readable file inspection |
| btop | top replacement | Interactive CPU, memory, and process view | Better system monitoring |
| duf | df replacement | Human-friendly disk usage tables | Easier disk inspection |
| httpie | HTTP client | Readable HTTP requests and responses | Friendly API debugging |
Alias Design
| Traditional command | Replacement | Intent |
|---|---|---|
ls | eza --icons=auto --group-directories-first | Keep the habit, improve output |
ll | eza -lh --icons=auto --group-directories-first --git | Long listing with Git status |
la | eza -lah --icons=auto --group-directories-first --git | Include hidden files |
l | eza -1 --icons=auto --group-directories-first | Fast one-column listing |
lt | eza --tree --level=2 --icons=auto --group-directories-first | Shallow tree view |
cat | bat | Syntax-highlighted reading by default |
grep | rg | Faster text search |
find | fd | Simpler file search |
top | btop | Modern resource monitor |
df | duf | Readable disk usage |
y | wrapper around yazi --cwd-file | Open the terminal file manager and change the current shell to Yazi’s final directory after exit |
yy | compatibility alias to y | Preserve the earlier wrapper name with the same behavior as y |
These aliases should be conditional. If a tool is missing on a new machine, the shell should still start.
One detail is easy to get wrong: alias y="yazi" alone cannot make the parent shell change directory after Yazi exits. A child process cannot directly mutate the parent shell’s cwd. The more reliable pattern is the official --cwd-file approach wrapped in a thin y() shell function: Yazi writes its final directory to a temporary file, then the function reads it and runs cd in the current shell. Nested Yazi protection should also live inside that function, rather than putting [[ -n "$YAZI_LEVEL" ]] && exit at the top level of .zshrc.
Yazi previews work best with a few supporting tools: ffmpegthumbnailer for video thumbnails, sevenzip for archives, poppler for PDFs, imagemagick for image processing, and jq for JSON. Installing them with the terminal stack keeps Yazi useful immediately on a fresh machine.
fzf, fd, bat, And eza As A Closed Loop
fzf gets much more useful when its data source and preview command are configured. The current setup uses fd for file and directory discovery, bat for file previews, and eza --tree for directory previews:
export FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix --hidden --follow --exclude .git'
export FZF_CTRL_T_COMMAND='fd --strip-cwd-prefix --hidden --follow --exclude .git'
export FZF_ALT_C_COMMAND='fd --type d --strip-cwd-prefix --hidden --follow --exclude .git'
export FZF_DEFAULT_OPTS="--ansi --preview 'if [ -d {} ]; then eza --tree --level=2 --icons=auto --group-directories-first {}; elif command -v bat >/dev/null 2>&1; then bat --style=numbers --color=always --line-range :500 {}; else sed -n \"1,500p\" {}; fi'"
These variables should be set before source <(fzf --zsh). The fzf loader is still guarded with [[ -t 0 && -o zle ]] so automation and non-interactive shells do not pay for interactive ZLE setup or produce noisy output.
Preserve History
Command history is part of the working environment. A terminal migration should not lose it. The setup keeps using the existing ~/.zsh_history:
export HISTFILE="${HISTFILE:-$HOME/.zsh_history}"
export HISTSIZE=100000
export SAVEHIST=100000
setopt APPEND_HISTORY
setopt EXTENDED_HISTORY
setopt INC_APPEND_HISTORY
setopt SHARE_HISTORY
setopt HIST_IGNORE_DUPS
setopt HIST_REDUCE_BLANKS
setopt HIST_VERIFY
After opening a new Ghostty window, the old history remains available to zsh, fzf, and zsh-autosuggestions.
Why Not Keep Adding To Oh My Zsh
Oh My Zsh is excellent for getting started. Over time, however, it can make the startup path harder to reason about. Themes, plugins, completion behavior, paste behavior, and framework defaults can become entangled.
This stack takes a narrower approach:
| Goal | Approach |
|---|---|
| Faster startup | Lazy-load interactive plugins with zinit |
| Maintainable prompt | Use Starship for prompt rendering |
| History preservation | Keep ~/.zsh_history as the history file |
| Rollback safety | Comment old config instead of deleting it |
| Cleaner automation | Load TTY-only integrations only in real terminals |
| Better daily commands | Replace legacy commands with modern CLI tools |
Theme Preferences
Ghostty theme names must come from ghostty +list-themes. Invalid names fail config loading. My preferred dark themes are:
| Priority | Theme | Style |
|---|---|---|
| 1 | TokyoNight Night | Deep Tokyo Night style, calm and suitable for long coding sessions |
| 2 | TokyoNight Storm | Similar family with slightly different contrast |
| 3 | Terminal Basic Dark | Closer to the classic macOS Terminal dark look |
The current default is:
theme = "TokyoNight Night"
Always validate after changing it:
ghostty +validate-config --config-file="$HOME/.config/ghostty/config"
Idempotent Installation
This setup works best as a repeatable installer. The installer should:
- Check macOS and Homebrew.
- Install Ghostty, Maple Mono NF CN, zinit, Starship, zoxide, fzf, Yazi, and modern CLI tools.
- Comment legacy Oh My Zsh and direct plugin loading lines instead of deleting them.
- Manage
~/.zshrcwith marker blocks so reruns replace the block instead of appending duplicates. - Write Ghostty, Starship, and Yazi configs.
- Configure the
fd,bat, andezapreview loop before sourcing fzf. - Prime zinit plugins once so the first real terminal does not wait on cloning.
- Validate
~/.zshrcand Ghostty config.
flowchart TD
A[Run installer] --> B{Homebrew available?}
B -- No --> C[Ask user to install Homebrew]
B -- Yes --> D[Install formulae and casks]
D --> E[Backup and update zshrc]
E --> F[Write Ghostty config]
F --> G[Write Starship and Yazi configs]
G --> H[Prime zinit plugins]
H --> I[Validate zsh, Yazi, and Ghostty config]
I --> J[Open a new Ghostty window]
Ghostty And Tool Usage Cheat Sheet
This section collects the commands and shortcuts used most often in the stack.
Ghostty
The current config uses Maple Mono NF CN so Latin, CJK, and Nerd Font icons
share a consistent monospace baseline:
font-family = "Maple Mono NF CN"
Common commands:
# Validate the config
ghostty +validate-config --config-file="$HOME/.config/ghostty/config"
# Reload config in running Ghostty windows
ghostty +reload-config
# List font families Ghostty can use
ghostty +list-fonts | rg -i "Maple Mono NF CN"
# List theme names; only names from this list are valid in config
ghostty +list-themes
zsh Plugins
zsh-autosuggestions: when a grey history suggestion appears, pressRightorEndto accept it; keep typing to ignore it.fast-syntax-highlighting: no extra action needed; commands, arguments, and paths are highlighted as you type.
Starship
Starship renders the prompt, including Git status, directory, language versions, and command duration:
starship --version
cat ~/.config/starship.toml
zoxide
The stack initializes zoxide with --cmd cd, so cd is already smart:
cd projects
cd mobile-workspace
cd -
# Explicitly query zoxide
zoxide query <keyword>
fzf
| Shortcut | Purpose |
|---|---|
Ctrl-R | Search shell history (Atuin takes over when enabled) |
Ctrl-T | Fuzzy file selection |
Alt-C | Fuzzy directory jump |
**<TAB> | Shell completion through fzf |
The config uses fd as the data source, bat for file previews, and
eza --tree for directory previews.
Yazi
Open Yazi with y (yy is a compatibility alias). After exit, the y()
wrapper changes the parent shell to Yazi’s final directory.
| Key | Action |
|---|---|
h | Go to parent directory |
l | Enter directory or open file |
j / k | Move down / up |
Enter | Open selected item |
Space | Toggle selection |
y | Yank/copy selection |
d | Delete selection |
p | Paste |
r | Rename |
a | Create file |
/ | Search |
n / N | Next / previous search result |
~ | Go home |
q | Quit |
Press ~ or F1 inside Yazi for the full help and keymap.
Atuin
atuin search <keyword>
atuin stats
atuin history
atuin daemon status
When Atuin is active, Ctrl-R opens the Atuin history search UI.
Modern CLI Aliases
ll # eza long listing with Git status
la # eza long listing including hidden files
lt # eza two-level tree view
rg PATTERN # ripgrep full-text search
fd NAME # fd file search
bat FILE # bat syntax-highlighted file view
btop # interactive resource monitor
duf # human-readable disk usage
http GET https://example.com
Why This Setup Works
The result is a clean terminal stack:
- Ghostty and Maple Mono NF CN handle rendering, so Latin, CJK, and icons stay on one monospace baseline.
- zsh keeps only essential shell behavior.
- zinit handles lazy plugin loading.
- Starship handles the prompt.
- zoxide handles navigation.
- fzf handles fuzzy interaction.
- Yazi handles terminal-native file browsing and previews.
- Modern CLI tools handle readable listings, search, file inspection, resource monitoring, disk usage, and HTTP debugging.
- Old config remains available but no longer participates in startup.
This is not about making the terminal flashy. It is about making the interaction path explicit: rendering, completion, search, prompt, navigation, and command readability each have a clear owner. That makes the environment easier to migrate, debug, and maintain.