Skip to content

Theme cookbook

Six complete themes, ready to copy into your app. Each one is a file in examples/theming/ and draws the same screen (preview.py: buttons in every variant, a small form, badges, a callout and two stats), so you can compare how each theme changes it.

From a clone of the repository (see Running the examples):

Terminal window
uv run python examples/theming/brand.py

Replace brand.py with high_contrast.py, retro_terminal.py, glassy_dark.py, pastel.py or mixed.py.

A company palette in two versions that follow the system’s light or dark mode. Theme.generate builds each version from three colors; derive sets the brand’s own green for success.

The preview screen with the Acme brand themeThe preview screen with the Acme brand theme
Acme Dark and, with the site in light mode, Acme Light.
examples/theming/brand.py
"""A brand theme in light and dark that follows the operating system.
python examples/theming/brand.py
- ``Theme.generate`` builds a whole theme from a background, a text and an accent color.
- ``derive`` changes a few tokens on top (rounder corners, the brand's own green for success).
- ``themes.register`` gives the themes a name, and ``themes.auto`` picks light or dark like the OS.
"""
import pyzed as pz
from preview import preview
BRAND = "#6d28d9" # the brand's purple
ACME_LIGHT = pz.Theme.generate(
"Acme Light", background="#ffffff", text="#1f1b2e", accent=BRAND, radius=10,
).derive(success="#15803d")
ACME_DARK = pz.Theme.generate(
"Acme Dark", background="#16131f", text="#ece9f5", accent="#a78bfa", radius=10,
).derive(success="#4ade80")
pz.themes.register(ACME_LIGHT)
pz.themes.register(ACME_DARK)
class BrandApp(pz.App):
title = "Acme"
size = (720, 580)
theme = pz.themes.auto("Acme Light", "Acme Dark") # light or dark, like the system
def view(self):
return pz.container(preview("Acme", "A brand theme that follows the system's light or dark mode."),
padding=24, width="fill", height="fill")
if __name__ == "__main__":
pz.run(BrandApp)

What to change: the three colors of each generate, and radius for rounder or squarer corners.

Pure black and white with yellow focus, for people who need strong contrast (and a good test of how your app looks with large, clear borders). Theme.from_dict lists every token that changes; the rest come from One Dark. The script prints the contrast ratio of the main colors when it starts.

The preview screen in black, white and yellow
High contrast: white borders, yellow accent, square corners.
examples/theming/high_contrast.py
"""A high-contrast theme for readers who need strong colors and clear edges.
python examples/theming/high_contrast.py
- ``Theme.from_dict`` starts from One Dark and replaces only the tokens you list.
- ``pz.theme.contrast`` checks the WCAG contrast ratio of two colors (7:1 or more is "AAA").
- A global ``style`` makes every text bigger.
"""
import pyzed as pz
from preview import preview
HIGH_CONTRAST = pz.Theme.from_dict({
"name": "High Contrast",
"appearance": "dark",
# surfaces: pure black, panels a touch lighter so they stay visible
"background": "#000000", "surface": "#0a0a0a", "elevated": "#141414", "editor": "#000000",
"panel": "#0a0a0a", "title_bar": "#000000", "status_bar": "#000000",
# strong white borders
"border": "#ffffff", "border_variant": "#bdbdbd",
# text: white, and muted text that is still very readable
"text": "#ffffff", "text_muted": "#e0e0e0", "text_placeholder": "#c2c2c2", "text_disabled": "#9e9e9e",
"icon": "#ffffff", "icon_muted": "#e0e0e0",
# buttons and list rows
"element": "#1f1f1f", "element_hover": "#333333", "element_active": "#4d4d4d",
"ghost_hover": "#262626", "ghost_active": "#3d3d3d", "ghost_selected": "#3d3d3d",
# status colors that stay readable on black
"error": "#ff6b6b", "warning": "#ffd600", "success": "#5cff8a", "info": "#6bc8ff",
# square corners
"radius": 0, "radius_sm": 0,
}).with_accent("#ffd600") # yellow focus rings, selection and primary buttons
pz.themes.register(HIGH_CONTRAST)
def report(theme):
"""Prints the contrast ratio of the main text colors against the background."""
for token in ("text", "text_muted", "accent", "error"):
ratio = pz.theme.contrast(theme.color(token), theme.background)
print(f"{token:>10} on background: {ratio:4.1f}:1")
class HighContrastApp(pz.App):
title = "High contrast"
size = (720, 580)
theme = "High Contrast"
style = pz.Style(size=15) # every text that does not set its own size
def view(self):
return pz.container(preview("High contrast", "Black, white and yellow, with square corners."),
padding=24, width="fill", height="fill")
if __name__ == "__main__":
report(HIGH_CONTRAST)
pz.run(HighContrastApp)

Green phosphor on black. One green becomes the text, the accent and the success color; the global style makes every text monospaced.

The preview screen in green on black with a monospaced font
Retro terminal: everything derives from one green.
examples/theming/retro_terminal.py
"""A retro terminal theme: green phosphor on black, monospaced and square.
python examples/theming/retro_terminal.py
- ``Theme.generate`` with a green text color and a green accent.
- ``syntax`` and ``ansi`` recolor code and the terminal to match.
- The global style switches every text to the monospaced font.
"""
import pyzed as pz
from preview import preview
PHOSPHOR = "#33ff66"
RETRO = pz.Theme.generate(
"Retro Terminal",
background="#050a05",
text=PHOSPHOR,
accent=PHOSPHOR,
error="#ff5555",
warning="#ffcc33",
success=PHOSPHOR,
radius=0,
syntax={"keyword": ("#a6ff00", "bold"), "string": "#7dffb0", "comment": ("#1f7a3a", "italic")},
).derive(ansi={"green": PHOSPHOR, "bright_black": "#1f7a3a"})
pz.themes.register(RETRO)
class RetroApp(pz.App):
title = "Retro terminal"
size = (720, 640)
theme = "Retro Terminal"
style = pz.Style(font="mono", size=13) # every text in the window is monospaced
def view(self):
title = pz.text("> READY_").style(size=26, weight="bold", color="accent")
return pz.container(pz.column([title, preview("SYSTEM", "All colors come from one green.")], spacing=12),
padding=24, width="fill", height="fill")
if __name__ == "__main__":
pz.run(RetroApp)

A gradient behind the window and a frosted glass panel on top. The theme is One Dark with a cyan accent; the look comes from the global style (background= a gradient) and the glass effect.

A frosted panel over a blue and purple gradient
Glassy dark: a gradient window and a glass panel.
examples/theming/glassy_dark.py
"""A glassy dark look: a gradient behind the window and a frosted panel on top.
python examples/theming/glassy_dark.py
- The global style paints the window with a ``pz.Gradient``.
- ``glass`` turns a container into a translucent panel that blurs what is behind it.
- The theme itself is One Dark with a cyan accent (``with_accent``) and rounder corners.
"""
import pyzed as pz
from preview import preview
GLASSY = pz.themes.ONE_DARK.derive(name="Glassy Dark", radius=14, radius_sm=10).with_accent("#22d3ee")
pz.themes.register(GLASSY)
NIGHT = pz.Gradient(["#0f172a", "#312e81", "#701a75"], angle=135)
class GlassyApp(pz.App):
title = "Glassy dark"
size = (760, 650)
theme = "Glassy Dark"
style = pz.Style(background=NIGHT) # paints the whole window
def view(self):
panel = pz.container(preview("Glassy", "A frosted panel over a gradient."), width="fill").style(
glass={"tint": "#ffffff", "strength": 0.08, "blur": 24}, radius=20, padding=24,
border=(1, "#ffffff22"))
return pz.container(panel, padding=28, width="fill", height="fill")
if __name__ == "__main__":
pz.run(GlassyApp)

A cream background, candy colors and very round corners. derive sets pastel avatar colors (players), and a named style (pz.define_style("bubble", ...)) draws the card around the screen.

The preview screen in pastel colors
Pastel: a light theme with pink accent and round corners.
examples/theming/pastel.py
"""A playful pastel theme: cream background, candy colors and very round corners.
python examples/theming/pastel.py
- ``Theme.generate`` for a light theme, then ``derive`` with a pastel info color and avatar colors.
- ``pz.define_style`` names a reusable "bubble" style for the card around everything.
"""
import pyzed as pz
from pyzed import ui
from preview import preview
PASTEL = pz.Theme.generate(
"Pastel", background="#fffaf3", text="#4a3f55", accent="#f472b6", radius=16,
success="#34d399", warning="#f59e0b", error="#fb7185",
).derive(info="#60a5fa", players=("#f472b6", "#a78bfa", "#60a5fa", "#34d399", "#fbbf24"))
pz.themes.register(PASTEL)
pz.define_style("bubble", background="surface", radius=24, padding=20, border=(2, "#f9a8d4"),
shadow=("#f472b633", (0, 8), 24))
class PastelApp(pz.App):
title = "Pastel"
size = (720, 670)
theme = "Pastel"
def view(self):
friends = pz.row([ui.avatar(name, index=i, size=30) for i, name in enumerate(["AL", "BO", "CY", "DI", "EV"])],
spacing=6)
return pz.container(pz.column([
pz.container(preview("Hello!", "Soft colors and round corners."), width="fill").style("bubble"),
friends,
], spacing=16), padding=24, width="fill", height="fill")
if __name__ == "__main__":
pz.run(PastelApp)

A light sidebar in a dark app, with a theme picker that saves the chosen theme to a JSON file and loads it on the next start (Theme.save and Theme.load).

A light settings sidebar next to a dark main area
with theme: draws the sidebar with One Light.
examples/theming/mixed.py
"""Two themes in one window, and a theme picker that remembers your choice.
python examples/theming/mixed.py
- ``with theme:`` draws a block with another theme (here, a light sidebar in a dark app).
- ``self.theme = name`` changes the theme at runtime; the whole window restyles at once.
- ``Theme.save`` and ``Theme.load`` keep the chosen theme in a JSON file.
"""
from pathlib import Path
import pyzed as pz
from pyzed import ui
from preview import preview
SAVED = Path(__file__).with_name("my-theme.json")
class MixedApp(pz.App):
title = "Mixed themes"
size = (980, 580)
def __init__(self):
self.theme = "One Dark"
if SAVED.exists():
self.theme = pz.themes.register(pz.Theme.load(SAVED)).name
def pick(self, name):
self.theme = name
pz.themes.get(name).save(SAVED) # remembered for the next start
def view(self):
current = self.resolved_theme().name
with pz.themes.get("One Light"): # everything built in this block uses One Light
sidebar = pz.container(pz.column([
ui.heading("Settings", level=3),
ui.field("Theme", ui.select(pz.themes.names(), current, self.pick, width=200)),
ui.muted("This panel always uses One Light."),
], spacing=12), padding=16, width=300, height="fill").style(background="background")
main = pz.container(preview(current, "The main area follows the theme you pick."),
padding=24, width="fill", height="fill")
return pz.row([sidebar, main], height="fill")
if __name__ == "__main__":
pz.run(MixedApp)
examples/theming/preview.py
"""A small "kitchen sink" used by every example in this folder.
It only uses theme tokens and ``pyzed.ui`` components, so it takes the look of whatever
theme is current: run any of the examples next to it to see the same screen restyled.
python examples/theming/brand.py
"""
import pyzed as pz
from pyzed import ui
def preview(title: str = "Preview", subtitle: str = "Every color on this screen comes from the theme."):
"""A heading, buttons in every variant, a small form, badges, a callout and two stats."""
buttons = pz.row([
ui.button("Primary", lambda: None, variant="solid"),
ui.button("Filled", lambda: None),
ui.button("Outline", lambda: None, variant="outline"),
ui.button("Ghost", lambda: None, variant="ghost"),
ui.button("Delete", lambda: None, variant="tinted", tint="error", icon="trash-2"),
], spacing=8, wrap=True)
form = pz.column([
ui.field("Name", ui.input("Ada Lovelace", lambda _v: None, width=220)),
ui.field("Email", ui.input("", lambda _v: None, placeholder="you@example.com", width=220)),
ui.field("Notifications", ui.switch(True, lambda _v: None)),
ui.field("Volume", pz.container(ui.slider(0.6, lambda _v: None), width=220)),
], spacing=10)
badges = pz.row([
ui.badge("New"),
ui.badge("Paid", tint="success"),
ui.badge("Pending", tint="warning"),
ui.badge("Failed", tint="error", variant="outline"),
ui.badge("Beta", tint="info", variant="solid"),
], spacing=6, wrap=True)
stats = pz.row([
ui.stat("Revenue", "$12.4k", delta=8.2, width="fill"),
ui.stat("Refunds", "31", delta=-2.5, width="fill"),
], spacing=12)
return pz.column([
pz.column([ui.heading(title), ui.muted(subtitle)], spacing=4),
buttons,
ui.card(pz.column([form, ui.progress(0.7)], spacing=14), title="Account"),
badges,
ui.callout("Saved", "Your changes are stored on this device.", tint="success"),
stats,
], spacing=16)

Every color in it is a token or a tint, which is why one file looks right in six very different themes. Write your own views the same way.