feat(cli): add `omz generate theme` to scaffold custom themes
The problem
Custom themes are the most fun thing you can do with Oh My Zsh and also the most under-explained. custom/themes/example.zsh-theme is one PROMPT= line. To get anywhere from there you need to know prompt escapes (%~, %(?:a:b)), that $fg[red] has to be wrapped in %{ %} but %F{red} doesn't, that git_prompt_info exists and is styled through ZSH_THEME_GIT_PROMPT_*, and which of the 150+ themes in themes/ are worth cribbing from. All of that is scattered across the wiki, lib/, and other people's themes.
There's a second reason this matters: we don't accept new themes upstream anymore. Custom themes are exactly where we want people making things... and then sharing them in Discord or on social, not in a PR. The tool should say so.
What I'm building: omz generate theme
The second generator, following #14045 / #14046. Same shape, same machinery.
Usage: omz generate theme [<name>] [options]
-s, --style <style> Starting point: minimal (default), two-line, or right
--no-git Leave git branch/status out of the prompt
--set Set it as your theme in .zshrc when done
(restarts your shell)
-y, --yes Never prompt; use defaults for anything not given
Creates $ZSH_CUSTOM/themes/<name>.zsh-theme. One file: the theme is the guide.
What a session looks like
$ omz generate theme
omz::generate::theme: Theme name: sunrise
omz::generate::theme: Starting point? [1] minimal one-liner [2] two lines [3] info on the right [1]: 1
omz::generate::theme: Show the git branch and status? [Y/n] y
omz::generate::theme: Set sunrise as your theme now? [y/N] n
create ~/.oh-my-zsh/custom/themes/sunrise.zsh-theme
omz::generate::theme: theme 'sunrise' generated.
Next steps:
1. Edit it: vim ~/.oh-my-zsh/custom/themes/sunrise.zsh-theme
2. Try it now: omz theme use sunrise
3. Keep it: omz theme set sunrise
4. Show it off: https://discord.gg/ohmyzsh (or tag @ohmyzsh with a screenshot)
omz theme use already exists and previews the theme in the current shell without touching .zshrc, which makes the edit/preview loop instant.
What gets generated
The header comment is the real feature. In order:
- How to try it (
omz theme use/omz theme set). - What kind of prompt do you want? Three shapes, each with a one-line sketch of what it looks like: minimal one-liner, two-line with the cursor alone on the second line, and short-left/info-right with
RPROMPT. The user picks one and deletes the rest. - Building blocks: a prompt-escape cheat sheet (
%n %m %~ %c %# %? %T %(?:ok:bad)), colors (%F{red},%B,%K{blue}, 256-color%F{208}, truecolor%F{#ff8800}, the$fg/$fg_bold/$reset_colorforms and why they need%{ %}, the eight named colors), and the*_prompt_infofunctions with a one-liner to list them all. - References: the wiki Themes and Customization pages, the zsh prompt-expansion docs, and
ls $ZSH/themesfor ideas. - Show it off: Discord, X, Mastodon, with a note that themes live in your own dotfiles rather than in Oh My Zsh and a link to the External themes wiki page.
Then a small, working prompt (arrow that goes red on failure, cyan directory, git info) with the two-line and RPROMPT variants present but commented out, and the ZSH_THEME_GIT_PROMPT_* settings explained.
sunrise.zsh-theme (minimal style, with git)# sunrise theme
#
# A custom Oh My Zsh theme. Try it in this shell without changing anything:
#
# omz theme use sunrise
#
# and keep it once you like it:
#
# omz theme set sunrise
#
# ---------------------------------------------------------------------------
# What kind of prompt do you want?
#
# Most prompts are one of three shapes. Pick the one that matches how you
# work and delete the others.
#
# 1. One line, minimal. Fast to read, leaves room for long commands.
# ➜ ohmyzsh git:(master) ✗
#
# 2. Two lines. Context on the first line, your cursor alone on the second.
# robby@laptop ~/projects/ohmyzsh git:(master) ✗
# ❯
#
# 3. Right-side info. Keep the left short and put the clock, exit code or
# git status on the right with RPROMPT.
# ➜ ohmyzsh master ✗ 14:20
#
# ---------------------------------------------------------------------------
# Building blocks
#
# Prompt escapes (the full list: man zshmisc, "EXPANSION OF PROMPT SEQUENCES")
# %n username %m hostname %~ directory (~ for $HOME)
# %c current dir name %2~ last two dirs %# # for root, % otherwise
# %? last exit code %T time (24h) %D{%a %b %d} strftime date
# %(?:ok:bad) "ok" if the last command succeeded, "bad" if it failed
#
# Colors. Oh My Zsh loads the `colors` module, so these all work:
# %F{red}text%f foreground, with %B ... %b for bold
# %K{blue}text%k background
# %F{208} 256 colors: https://www.ditig.com/256-colors-cheat-sheet
# %F{#ff8800} 24-bit, if your terminal supports it
# $fg[red] $fg_bold[red] $bg[red] $reset_color
# The $fg/$bg forms must be wrapped in %{ ... %} so zsh knows they take
# up no space, e.g. %{$fg[red]%}text%{$reset_color%}
# Named colors: black red green yellow blue magenta cyan white
#
# Info from plugins. Any *_prompt_info function can go straight in the prompt:
# $(git_prompt_info) branch and dirty state, styled by the
# ZSH_THEME_GIT_PROMPT_* variables below
# $(virtualenv_prompt_info) $(rvm_prompt_info) $(nvm_prompt_info)
# Find more with: print -l ${(k)functions[(I)*_prompt_info]}
#
# References
# https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
# https://github.com/ohmyzsh/ohmyzsh/wiki/Customization#overriding-and-adding-themes
# https://zsh.sourceforge.io/Doc/Release/Prompt-Expansion.html
# Themes that ship with Oh My Zsh, for ideas: ls $ZSH/themes
#
# ---------------------------------------------------------------------------
# Made something you like? Show it off!
#
# Discord: https://discord.gg/ohmyzsh (#themes)
# X: https://x.com/ohmyzsh Mastodon: https://mstdn.social/@ohmyzsh
#
# Tag @ohmyzsh and include a screenshot. Themes live in your own dotfiles or
# repo rather than in Oh My Zsh itself, so this is how other people find them:
# https://github.com/ohmyzsh/ohmyzsh/wiki/External-themes
# ---------------------------------------------------------------------------
# Green arrow when the last command succeeded, red when it failed
PROMPT='%(?:%F{green}➜:%F{red}➜)%f '
# Current directory
PROMPT+='%F{cyan}%c%f '
# Git branch and status (see the settings below)
PROMPT+='$(git_prompt_info)'
# Uncomment for a two-line prompt: everything above on line one, cursor below
# PROMPT+=$'\n'"%F{magenta}❯%f "
# Uncomment for a clock on the right
# RPROMPT='%F{240}%T%f'
# How git_prompt_info looks. Prefix and suffix wrap the branch name; dirty and
# clean are appended depending on whether there are uncommitted changes.
ZSH_THEME_GIT_PROMPT_PREFIX="%F{blue}git:(%F{red}"
ZSH_THEME_GIT_PROMPT_SUFFIX="%f "
ZSH_THEME_GIT_PROMPT_DIRTY="%F{blue}) %F{yellow}✗"
ZSH_THEME_GIT_PROMPT_CLEAN="%F{blue})"
--style two-line uncomments the second-line block; --style right uncomments RPROMPT and moves the git info into it; --no-git drops the git_prompt_info line and the ZSH_THEME_GIT_PROMPT_* block.
Design notes
Reuses the plugin generator's guts. ask, render and created from #14046 move from _omz::generate::plugin::* to _omz::generate::* so both generators share them. Templates go in templates/generators/theme/, next to templates/generators/plugin/, and get the same zsh -n in CI.
Single file, in $ZSH_CUSTOM/themes/. That's the newer of the two custom lookup locations ($ZSH_CUSTOM/<name>.zsh-theme still works for compatibility) and it's what omz theme list scans. No README: built-in themes are flat files and a theme's documentation is its header.
Style variants are one template plus a few toggled lines, not three templates. The two-line and right-side blocks are always present so the user can switch later by uncommenting. The generator just decides which lines start uncommented.
Same name rules as plugins (^[a-z0-9][a-z0-9_-]*$), for the same reason: the name ends up in a path and, via --set, in the ZSH_THEME= rewrite that omz theme set does. Existing custom theme → hard error. Same name as a built-in → warn and confirm, since custom overrides built-in by design.
The generated theme has to actually work, not just parse. The test sources lib/theme-and-appearance.zsh, lib/git.zsh and lib/prompt_info_functions.zsh in a zsh -f, sources the generated file, and runs print -P -- "$PROMPT". That catches a broken escape sequence, which zsh -n never would.
--set reuses _omz::theme::set, which does the .zshrc backup/rewrite/zsh -n/rollback dance and then reloads. Last thing the command does, after the next-steps output.
Out of scope for v1
Powerline/segment-style prompts, async git status, a p10k-style configuration wizard, previewing colors in the terminal, and anything that installs fonts. If people want those they'll find the themes that do them.
Filing this so there's a place to track it. Will start once #14046 lands so the shared helpers have a home.
Co-authored with Claude Code (used for the research and drafting this proposal; the template draft was sourced and its prompt expanded in a zsh -f to check it works)
Source: ohmyzsh/ohmyzsh