Creating a custom ZSH prompt
Intro
The goal of this post is to end up with a prompt like this:

But first, there are a number of great terminal prompts already available over at the oh-my-zsh repository on Github.
However, maybe you want to make one of your own and you're not sure where to start... I was in this position today, so I decided to figure it out.
Firstly, I took a bunch of inspiration from the themes that are already in the repository, and their corresponding source code.
I started with a theme called steeef and modified from there.
Colors
A natural first step was to figure out how to color things, which looked fairly simple...
orange="%F{166}"
Should give me the color orange, and it did. You can test these by simply typing in a terminal that is running ZSH:
print -P "%{%F{166}%} Hello World ---> $"
This will generate you a prompt that looks something like:

The next thing to figure out is why does 166 correspond to orange? These are a form of ANSI escape codes which are ways of signaling to the underlying terminal emulator things like color, cursor location, text decoration etc.
You can find a list of ANSI color codes in various places online.
Composing colors
To compose colors together, ensure that the color you want a specific word or symbol to be is placed just before the color, e.g. to alter the above prompt and have the word "World" appear in blue, it would be:
print -P "%{%F{166}%} Hello %{%F{4}%}World ---> $"
Which changes the above prompt to:

However, as you may have already noticed - this changes all the words in the prompt to the same color - which is probably not what you wanted...
In order to fix this issue, the concept of a reset is introduced:
reset="%f"
This can then be used in-between words, or at the end of your prompt to ensure that the commands entered after the prompt are also not colorized, e.g.
reset="%f"
print -P \
"%{%F{166}%} Hello %{%F{4}%}World%{$reset%} ---> $"
Precmd
precmd is a "hook" that is executed before each prompt. This is useful for
things like figuring out whether we should display git information, e.g. the
hook will be executed once you cd to a new directory - giving us a chance to
figure out whether this directory is under a VCS.
This can be used alongside a ZSH plugin called vcs_info in the following way:
precmd() {
zstyle ':vcs_info:*' formats '%F{15}on %F{61}%b'
vcs_info
}
The
vcs_info
function above handles the heavy lifting for us, such as determining whether to
show VCS information. All we need to do is define a format for the prompt, which
you can see above we do with zstyle.
Putting everything together
It's possible to do anything you can normally do in a bash script in these theme files too, e.g. an if statement to determine whether we are a specific user or not, see below for more. The full theme presented at the start of this post can be achieved with the following:
autoload -Uz vcs_info
zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:*:prompt:*' check-for-changes true
# Colors
blue="%F{33}"
cyan="%F{37}"
green="%F{64}"
orange="%F{166}"
red="%F{124}"
reset="%f"
violet="%F{61}"
white="%F{15}"
# User specific options
user="%{$blue%}"
if [[ "$USER" == "root" ]]; then
user="%{$red%}"
fi
# Highlight that we're connected via SSH.
host="%{$cyan%}";
cloud=""
if [[ "$SSH_TTY" ]]; then
host="%{$terminfo[bold]$cyan%}";
cloud=☁️
fi
precmd() {
zstyle ':vcs_info:*' formats '%F{15}on %F{61}%b'
vcs_info
}
PROMPT=$'
%{$user%}%n %{$white%}at %{$host%}%m%{$cloud%} %{$terminfo[sgr0]%}%{$white%}in %{$green%}%~%{$reset%} ${vcs_info_msg_0_}%{$reset%}
$ '
There's nothing really magical going on here, we just use variables to define colors instead of repeating the ANSI escape codes over and over, and there are some if statements depending on which user we are, whether we're connected via SSH etc.
There's a lot more you can do like defining an RPROMPT etc. which will show on
the right side of the terminal emulator rather than the left - these are left as
an exercise for the reader.