Customization#

Make the shell yours: prompts, themes, completions, dotfile management.

Prompts#

Modern prompts are usually managed by a cross-shell tool rather than hand-coded. The tools below all handle async git status, language version pickers, command duration, and exit codes without slowing the prompt down, the kind of thing a hand-rolled PS1 struggles with.

  • Starship, the default in 2026; one binary, one starship.toml, works in Bash / Zsh / Fish / PowerShell / Nu / Cmd / Tcsh.

  • Powerlevel10k, Zsh only; extremely fast; configurable wizard on first run.

  • Oh My Posh, cross-shell, theme-driven, popular on Windows.

  • pure, minimal Zsh prompt.

Why a tool over hand-rolling: async git status, language-version indicators, command duration, exit code, jobs, all without slowing the prompt to a crawl.

A minimal Starship config:

# ~/.config/starship.toml
format = "$directory$git_branch$git_status$character"
add_newline = false

[character]
success_symbol = "[➜](bold green)"
error_symbol = "[✗](bold red)"

Fonts#

Many prompts use icons (git branch glyph, OS logo, language version markers) that the default monospace fonts do not contain. A Nerd-Font-patched terminal font fills those glyph slots so the prompt renders correctly across every shell and host you set up:

  • Nerd Fonts, patched fonts with programming ligatures and icon glyphs.

  • Common picks: FiraCode Nerd Font, JetBrainsMono Nerd Font, Hack Nerd Font, Iosevka.

Theming Terminals#

The terminal emulator’s color scheme matters as much as the shell’s – many “shell themes” are really terminal themes plus a matching prompt. Most modern terminals support theme files in a common format and converge on the same dozen popular schemes:

Plugins#

Each shell has its own plugin culture, with frameworks for the beginner-friendly path and lighter managers for users who want to keep the config tight. The picks below are the names that come up most in 2026:

Bash:

Zsh:

  • Plugin managers: zinit, antidote, Oh My Zsh, Prezto.

  • Top plugins: zsh-autosuggestions, zsh-syntax-highlighting, zsh-history-substring-search, zsh-completions, fzf-tab.

Fish:

  • Plugin managers: Fisher, Oh My Fish.

  • Top plugins: z (jump to recent dirs), done (notify on long commands), puffer (text expansion).

Nu and PowerShell have plugin / module systems but smaller ecosystems.

Universal Tools#

These work in any shell and are usually the highest-impact upgrades you can make. Most of them are Rust- or Go-binary replacements for the classic Unix tools, with sensible colored output, faster execution, and ergonomic flag defaults:

  • fzf, fuzzy finder. Once installed, the default keybinds are:

    Keybind

    Effect

    Ctrl-R

    Fuzzy search shell history.

    Ctrl-T

    Fuzzy search files under the working directory; insert the pick on the prompt.

    Alt-C

    Fuzzy search directories and cd into the pick.

  • zoxide, smarter cd; z foo jumps to the most-frecent directory matching “foo”.

  • eza, a modern ls with colors and Git integration.

  • bat, cat with syntax highlighting.

  • ripgrep (rg), much faster grep.

  • fd, friendlier find.

  • delta, prettier git diff.

  • jq / yq, query / transform JSON / YAML.

  • direnv, per-directory environment variables.

  • mise / asdf – per-project language version managers.

Most of these install in seconds and pay back many times.

Completion Sources#

Most CLI tools ship completion scripts that you generate from the tool itself and source from the shell’s completion directory. carapace-bin generates completions for hundreds of tools from a single source, which is the cleanest path if you want consistent completion across many shells:

$ kubectl completion bash > ~/.bash_completion.d/kubectl
$ gh completion -s zsh > ~/.zfunc/_gh
$ docker completion fish | source

Tools like carapace-bin provide completions across many shells from one source.

Dotfiles#

Manage shell config (and the rest of the user environment) in a Git repo so it travels with you. The tools below all do the same job, get a fresh laptop into your preferred configuration, but differ on templating, secrets handling, and how much they hide the underlying Git operations:

  • Plain Git + symlinks, ln -s ~/dotfiles/.zshrc ~/.zshrc.

  • chezmoi, declarative, supports templating per host / OS / user; the popular choice in 2026.

  • yadm, yadm = “yet another dotfiles manager”; Git wrapper that uses $HOME as the work tree.

  • dotbot, YAML-driven installer.

  • GNU Stow, the classic symlink farm manager.

Common patterns:

  • Per-machine ~/.local.zshrc sourced from the main file.

  • Templated values for hostnames, work-vs-personal differences.

  • Encrypted secrets via age / GPG / SOPS.

  • Bootstrap script that sets up everything fresh.

History Across Machines#

Standard shell history lives in one file on one machine. The tools below replace Ctrl-R with a sync layer that follows you between hosts, useful for operators who switch between laptop, jump host, and a couple of long-running VMs throughout a day.

  • Atuin, self-hosted, end-to-end encrypted shell history sync; replaces Ctrl-R.

  • mcfly, neural-network ranked history search.

Not for everyone, but life-changing if you switch between machines a lot.

Performance#

A slow-starting shell is paid for on every new terminal, every ssh, every script -c, every container exec. The fixes below are the standard ones, profile to find the offenders, then defer or remove the slowest plugins and language-version managers:

  • Profile, Bash: PS4='+$EPOCHREALTIME ' bash -lixc exit; Zsh: zmodload zsh/zprof then zprof; Fish has built-in startup profiling.

  • Lazy load completions and language-version managers (nvm, rbenv).

  • Pin slow plugins behind async loaders or remove them.

  • Smaller prompt, prompt managers are usually the slowest single thing, but they’re tunable.

Aim for sub-100ms shell startup; sub-50ms is achievable with care.

Aliases vs. Functions vs. Scripts#

A useful hierarchy for “where does this code go?” Each tier hides its predecessor from view, once a function exists, you reach for the function name, not the underlying commands; once a script exists, you stop maintaining the function form. The complexity of the task picks the tier:

  • Alias, one-liner shorthand. alias gs='git status'.

  • Function, multi-line logic, takes arguments.

  • Script, complex enough to deserve its own file in $PATH and source control.

When a function grows past ~30 lines, promote it to a script with proper argument parsing.