Creating themes
There are four ways to make a theme, from the quickest to the most detailed. All of them return a Theme, which
you can use directly (theme = my_theme) or register to use by name.
| You want to… | Use | Effort |
|---|---|---|
| Change only the brand color | theme.with_accent("#e11d48") |
One line |
| Change a few tokens of an existing theme | theme.derive(radius=12, success="#16a34a") |
A few lines |
| A whole new theme from your colors | pz.Theme.generate(name, background=, text=, accent=) |
Three colors |
| Full control, or a theme in a file | pz.Theme.from_dict({...}), Theme.load("theme.json") |
Any token |
Themes are immutable: every method returns a new theme and leaves the original untouched, so
pz.themes.ONE_DARK is always the real One Dark.
1. Change the accent
Section titled “1. Change the accent”The accent is the color of primary buttons, switches, sliders, focus rings, links and selections. with_accent
changes all of them at once and picks a readable on_accent (the text on top of the accent):
class MyApp(pz.App): theme = pz.themes.ONE_DARK.with_accent("#e11d48") # One Dark, in red2. Derive: change a few tokens
Section titled “2. Derive: change a few tokens”derive copies a theme with the tokens you pass. The syntax and ansi dictionaries are
merged with the original, so you only list what changes:
ocean = pz.themes.ONE_DARK.derive( name="Ocean", background="#0b1622", surface="#102131", radius=10, radius_sm=8, syntax={"keyword": "#ff79c6", "comment": ("#5c6f80", "italic")},).with_accent("#4fb3ff")3. Generate a theme from three colors
Section titled “3. Generate a theme from three colors”Theme.generate builds every token from a background, a text color and an accent: surfaces, borders,
hover states, muted text and the editor colors are blended between the background and the text.
custom = pz.Theme.generate( "Forest", background="#14201a", # where content sits (the editor token); the window is blended a bit lighter text="#e3efe6", # the main text color accent="#4ade80", # buttons, focus, links)Optional arguments:
| Argument | Default | Meaning |
|---|---|---|
appearance |
guessed | "dark" or "light"; guessed from how bright background is. |
error, warning, success |
from One Dark / One Light | Status colors (they also color the version control tokens). |
syntax |
One’s colors | Code colors to change (merged). |
radius |
6.0 |
Corner radius; radius_sm becomes radius - 2. |
When you have a whole palette (the chrome around the editor and six hues for status and syntax),
theme_palettes.from_palette uses every color the way Zed’s theme families do. Pyzed’s own themes (Midnight,
Graphite, Forest, Rosé, Dawn, Glacier) are made with it:
from pyzed import theme_palettes
dusk = theme_palettes.from_palette( "Dusk", "dark", editor="#1b1d24", chrome="#23262f", text="#d8dbe2", accent="#7aa2f7", red="#f7768e", orange="#ff9e64", yellow="#e0af68", green="#9ece6a", cyan="#7dcfff", purple="#bb9af7",)4. Every token, from a dictionary or a file
Section titled “4. Every token, from a dictionary or a file”Theme.from_dict takes any tokens you list and fills in the rest from One Dark (or One Light when
"appearance": "light"). Unknown names raise a ValueError, so typos do not go unnoticed:
paper = pz.Theme.from_dict({ "name": "Paper", "appearance": "light", "background": "#f5f1e8", "surface": "#fbf8f1", "editor": "#fffdf8", "text": "#2b2620", "text_muted": "#6b6257", "border": "#d9d0c1", "accent": "#b45309", "on_accent": "#ffffff",})Save and load a theme file
Section titled “Save and load a theme file”save writes a theme as JSON and Theme.load reads it back. The file has the same keys as from_dict, so you can
also write it by hand (only the tokens you change are needed):
{ "name": "Paper", "appearance": "light", "background": "#f5f1e8", "text": "#2b2620", "accent": "#b45309", "on_accent": "#ffffff", "radius": 2}paper = pz.Theme.load("themes/paper.json")paper.save("themes/paper-full.json") # every token, ready to editImport a Zed theme
Section titled “Import a Zed theme”Because the token names follow the Zed editor’s theme format, any theme from the
Zed theme gallery works in Pyzed. Download its .json file and:
mocha = pz.Theme.from_zed("catppuccin.json", name="Catppuccin Mocha") # one theme of the filepz.themes.load_zed("catppuccin.json") # every theme in it, registeredGive it a name
Section titled “Give it a name”pz.themes.register adds a theme to the list of themes, so it can be used by name, shows up in
pz.themes.names() (and in any theme picker built from it), and works with pz.themes.auto:
-
Create the themes, usually in their own module:
mytheme.py import pyzed as pzLIGHT = pz.themes.register(pz.Theme.generate("Acme Light", background="#ffffff", text="#1f1b2e", accent="#6d28d9"))DARK = pz.themes.register(pz.Theme.generate("Acme Dark", background="#16131f", text="#ece9f5", accent="#a78bfa")) -
Import that module before the app starts, and use the names:
main.py import pyzed as pzimport mytheme # noqa: F401 - registers the Acme themesclass MyApp(pz.App):theme = pz.themes.auto("Acme Light", "Acme Dark") -
Run it:
Terminal window uv run python main.pymacOS and Linux python3 main.pyWindows (PowerShell) py main.py
Registering a theme with the name of an existing one replaces it.
Check the contrast
Section titled “Check the contrast”pyzed.theme has small color helpers, handy when you build themes in code:
| Function | Returns |
|---|---|
contrast(a, b) |
The WCAG contrast ratio of two colors, from 1 to 21. Text should be at least 4.5 (AA), ideally 7 (AAA). |
readable_on(background) |
A light or a dark text color, whichever reads better on background. |
luminance(color) |
How bright a color is, from 0 to 1. |
mix(a, b, amount) |
A color between a and b (amount 0 is a, 1 is b). |
lighten(color, amount), darken(color, amount) |
The color mixed with white or black. |
alpha(color, value) |
The color with an opacity (alpha("#ff0000", 0.5) is #ff000080). |
from pyzed.theme import contrast
t = pz.Theme.generate("Forest", background="#14201a", text="#e3efe6", accent="#4ade80")print(round(contrast(t.text, t.background), 1)) # 11.2 -> greatprint(round(contrast(t.text_muted, t.background), 1)) # 6.0 -> still above 4.5Complete examples
Section titled “Complete examples”The Theme cookbook has five complete themes to copy: a brand theme in light and dark, high contrast, retro terminal, glassy dark and pastel.