Themes¶
Click Extra exposes a small theming system that controls every color used in help screens: options, choices, metavars, arguments, CLI names, defaults, environment variables, log levels, and more. Two themes ship by default (dark and light), and downstream projects can register their own.
The --theme option¶
Every command and group built with @click_extra.command / @click_extra.group automatically gets a --theme flag, alongside the other default options.
from click_extra import command, echo
@command
def weather():
"""Show the local weather forecast."""
echo("Sunny, 22°C.")
$ weather --theme dark --help
Usage: weather [OPTIONS]
Show the local weather forecast.
Options:
--time / --no-time Measure and print elapsed execution time. [default:
no-time]
--color, --ansi / --no-color, --no-ansi
Strip out all colors and all ANSI codes from output.
[default: color]
--theme [dark|light] Color theme used for help screens. [default: dark]
--config CONFIG_PATH Location of the configuration file. Supports local
path with glob patterns or remote URL. [default: ~/.c
onfig/weather/{*.toml,*.yaml,*.yml,*.json,*.json5,*.js
onc,*.hjson,*.ini,*.xml,pyproject.toml}]
--no-config Ignore all configuration files and only use command
line parameters and environment variables.
--validate-config FILE Validate the configuration file and exit.
--show-params Show all CLI parameters, their provenance, defaults
and value, then exit.
--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
Rendering style of tables. [default: rounded-outline]
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one level
for each additional repetition of the option.
[default: 0]
--version Show the version and exit.
-h, --help Show this message and exit.
$ weather --theme light --help
Usage: weather [OPTIONS]
Show the local weather forecast.
Options:
--time / --no-time Measure and print elapsed execution time. [default:
no-time]
--color, --ansi / --no-color, --no-ansi
Strip out all colors and all ANSI codes from output.
[default: color]
--theme [dark|light] Color theme used for help screens. [default: dark]
--config CONFIG_PATH Location of the configuration file. Supports local
path with glob patterns or remote URL. [default: ~/.c
onfig/weather/{*.toml,*.yaml,*.yml,*.json,*.json5,*.js
onc,*.hjson,*.ini,*.xml,pyproject.toml}]
--no-config Ignore all configuration files and only use command
line parameters and environment variables.
--validate-config FILE Validate the configuration file and exit.
--show-params Show all CLI parameters, their provenance, defaults
and value, then exit.
--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
Rendering style of tables. [default: rounded-outline]
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one level
for each additional repetition of the option.
[default: 0]
--version Show the version and exit.
-h, --help Show this message and exit.
The flag is eager, so it is processed before any other option and before help is rendered. The picked theme is stored on the active Click contextās meta dict under click_extra.context.THEME, so it applies for the duration of the current invocation only: subcommands inherit it (Click shares meta across the parent/child hierarchy), but a sibling invocation of the same CLI in the same process starts from the default again. See the available keys table for the full inventory you can read from your own callbacks.
Configuration file¶
Like every other default option, --theme reads its value from pyproject.toml:
[tool.weather]
theme = "light"
Now invocations of the weather CLI pick up the light theme without passing --theme on the command line.
Built-in themes¶
Name |
Intended terminal background |
|---|---|
|
Dark background |
|
Light background |
Both themes are defined in click_extra.theme.HelpExtraTheme.dark() / .light(). The light factory swaps the dark paletteās bright variants and cyan accents for plain colors that stay legible on a white background.
The two module-level instances live in click_extra.theme:
default_theme: the process-wide fallback theme used when no Click context is active.ThemeOptiondoes not reassign it: per-invocation choices live onctx.metainstead.click_extra.wrap.patch_click()does reassign it to override the fallback for the entire patched session.nocolor_theme: an all-identitytheme used when ANSI rendering is suppressed.
Use click_extra.theme.get_current_theme() to read the theme that applies to the current invocation: it consults the active Click context first and falls back to default_theme.
Registering a custom theme¶
The list of valid --theme choices is pulled from click_extra.theme.theme_registry at option-instantiation time. To add your own theme, call register_theme() before declaring your commands:
from click_extra import (
HelpExtraTheme,
Style,
Color,
command,
echo,
register_theme,
)
def neon():
"""A high-contrast theme inspired by neon signage."""
return HelpExtraTheme.dark().with_(
heading=Style(fg=Color.bright_magenta, bold=True, underline=True),
option=Style(fg=Color.bright_cyan),
choice=Style(fg=Color.bright_yellow),
)
register_theme("neon", neon)
@command
def cocktail():
"""Mix a cocktail."""
echo("Cheers!")
After this runs, cocktail --theme neon becomes a valid invocation, and cocktail --help lists [dark|light|neon] as the choices.
The registry stores factories, not pre-built instances, so themes that depend on runtime state (terminal capabilities, environment variables, user settings) compute their styles lazily.
Caution
register_theme() mutates a module-level dict. Call it once at import time, before your @command / @group decorators run. ThemeOption builds its click.Choice from theme_registry at instantiation, so themes registered after the option is constructed will not appear in the choices.
Anatomy of a theme¶
HelpExtraTheme is a frozen dataclass that extends cloup.HelpTheme with extra styling slots for log levels and Click Extraās own categories.
Use with_() to derive a new theme that only overrides a few styles:
from click_extra import HelpExtraTheme, Style, Color
minimal = HelpExtraTheme.dark().with_(
option=Style(fg=Color.white),
choice=Style(fg=Color.white, dim=True),
)
with_() returns the same instance when no styles change and validates that all keyword arguments match a known field, so typos like optoin=... raise immediately.
Cross-reference highlighting¶
The cross_ref_highlight flag (default True) controls whether option names, choices, arguments, metavars, and CLI names are highlighted wherever they appear in free-form prose. Disable it for a calmer help screen:
calm = HelpExtraTheme.dark().with_(cross_ref_highlight=False)
See Cross-reference highlighting for the details on what stays styled when the flag is off.
Interaction with --color / --no-color¶
--theme controls which colors are used. --color / --no-color controls whether colors are emitted at all. The two are independent:
--theme lightwith--no-coloremits no ANSI codes.--theme darkwith--color(the default) emits the dark themeās ANSI codes.NO_COLOR=1in the environment overrides any--themechoice by silencing all ANSI output.
The --color callback inspects the standard set of color environment variables (NO_COLOR, CLICOLOR, FORCE_COLOR, LLM, etc.) before the theme is applied: see color_envvars for the full list.
click_extra.theme API¶
classDiagram
ExtraOption <|-- ThemeOption
HelpTheme <|-- HelpExtraTheme
Help-screen color themes for Click Extra.
Holds the HelpExtraTheme dataclass, the module-level
default_theme and nocolor_theme instances, the named-theme
theme_registry plus register_theme() helper, and the
ThemeOption that exposes --theme on every Click Extra command.
Note
The active theme for a CLI invocation is stored on the Click contextās
meta dict under THEME_META_KEY by ThemeOption. Use
get_current_theme() to retrieve it: that helper consults the active
Click context first and falls back to default_theme when no
context is in flight (e.g. at import time, in wrap patching, or in
bare REPL usage). Per-invocation context storage means concurrent
invocations of the same CLI in one process (Sphinx builds, test runners,
REPLs) do not leak --theme choices into each other.
- class click_extra.theme.HelpExtraTheme(invoked_command=<function identity>, command_help=<function identity>, heading=<function identity>, constraint=<function identity>, section_help=<function identity>, col1=<function identity>, col2=<function identity>, alias=<function identity>, alias_secondary=None, epilog=<function identity>, critical=<function identity>, error=<function identity>, warning=<function identity>, info=<function identity>, debug=<function identity>, option=<function identity>, subcommand=<function identity>, choice=<function identity>, metavar=<function identity>, bracket=<function identity>, envvar=<function identity>, default=<function identity>, range_label=<function identity>, required=<function identity>, argument=<function identity>, deprecated=<function identity>, search=<function identity>, success=<function identity>, cross_ref_highlight=True, subheading=<function identity>)[source]¶
Bases:
HelpThemeExtends
cloup.HelpThemewithlogging.levelsand extra properties.- cross_ref_highlight: bool = True¶
Highlight options, choices, arguments, metavars and CLI names in free-form text (descriptions, docstrings).
When
False, only structural elements are styled: bracket fields ([default: ...],[env var: ...], ranges,[required]), deprecated messages, and subcommand names in definition lists.
- subheading()¶
Non-canonical Click Extra properties.
Note
Subheading is used for sub-sections, like in the help of mail-deduplicate.
Todo
Maybe this shouldnāt be in Click Extra because it is a legacy inheritance from one of my other project.
- Return type:
TypeVar(T)
- with_(**kwargs)[source]¶
Derives a new theme from the current one, with some styles overridden.
Returns the same instance if the provided styles are the same as the current.
- Return type:
- static light()[source]¶
A theme assuming a light terminal background color.
Mirrors
dark()but swaps the palette for one that stays legible on a white background: bright variants (which most terminals render as washed-out tints) are replaced by their standard counterparts,bright_whitebecomesblack, and cyan accents becomebluesince cyan on white is hard to read.- Return type:
- click_extra.theme.default_theme: HelpExtraTheme = HelpExtraTheme(invoked_command=Style(fg='bright_white', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'bright_white', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), command_help=<function identity>, heading=Style(fg='bright_blue', bg=None, bold=True, dim=None, underline=True, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'bright_blue', 'bg': None, 'bold': True, 'dim': None, 'underline': True, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), constraint=Style(fg='magenta', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs=None), section_help=<function identity>, col1=<function identity>, col2=<function identity>, alias=Style(fg='cyan', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs=None), alias_secondary=Style(fg='cyan', bg=None, bold=None, dim=True, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs=None), epilog=<function identity>, critical=Style(fg='red', bg=None, bold=True, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'red', 'bg': None, 'bold': True, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), error=Style(fg='red', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'red', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), warning=Style(fg='yellow', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'yellow', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), info=<function identity>, debug=Style(fg='blue', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'blue', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), option=Style(fg='cyan', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'cyan', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), subcommand=Style(fg='cyan', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'cyan', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), choice=Style(fg='magenta', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'magenta', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), metavar=Style(fg='cyan', bg=None, bold=None, dim=True, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'cyan', 'bg': None, 'bold': None, 'dim': True, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), bracket=Style(fg=None, bg=None, bold=None, dim=True, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': None, 'bg': None, 'bold': None, 'dim': True, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), envvar=Style(fg='yellow', bg=None, bold=None, dim=True, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'yellow', 'bg': None, 'bold': None, 'dim': True, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), default=Style(fg='green', bg=None, bold=None, dim=True, underline=None, overline=None, italic=True, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'green', 'bg': None, 'bold': None, 'dim': True, 'underline': None, 'overline': None, 'italic': True, 'blink': None, 'reverse': None, 'strikethrough': None}), range_label=Style(fg='cyan', bg=None, bold=None, dim=True, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'cyan', 'bg': None, 'bold': None, 'dim': True, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), required=Style(fg='red', bg=None, bold=None, dim=True, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'red', 'bg': None, 'bold': None, 'dim': True, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), argument=Style(fg='cyan', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'cyan', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), deprecated=Style(fg='bright_yellow', bg=None, bold=True, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'bright_yellow', 'bg': None, 'bold': True, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), search=Style(fg='green', bg=None, bold=True, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs=None), success=Style(fg='green', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs={'fg': 'green', 'bg': None, 'bold': None, 'dim': None, 'underline': None, 'overline': None, 'italic': None, 'blink': None, 'reverse': None, 'strikethrough': None}), cross_ref_highlight=True, subheading=Style(fg='blue', bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None, _style_kwargs=None))¶
Process-wide fallback theme.
Used by
get_current_theme()when no Click context is active or when the active context has no theme set.ThemeOptionwrites its picked theme toctx.metarather than reassigning this attribute, so per-invocation choices do not leak across CLI invocations sharing the same process.click_extra.wrap.patch_click()does reassign this attribute, by design:patch_clickis itself a process-wide monkey-patch, so a process-wide theme override matches its scope.
- click_extra.theme.get_current_theme()[source]¶
Return the theme active for the current CLI invocation.
Resolution order:
The theme stored on the active Click context under
click_extra.context.THEME(set byThemeOptionfrom--theme).The module-level
default_theme(the dark default, or whateverclick_extra.wrap.patch_click()set at process start).
Falling back through the active context (instead of reading a module attribute) keeps
--themescoped to the invocation that received it, so a second invocation in the same process starts from the default again.- Return type:
- click_extra.theme.nocolor_theme: HelpExtraTheme = HelpExtraTheme(invoked_command=<function identity>, command_help=<function identity>, heading=<function identity>, constraint=<function identity>, section_help=<function identity>, col1=<function identity>, col2=<function identity>, alias=<function identity>, alias_secondary=None, epilog=<function identity>, critical=<function identity>, error=<function identity>, warning=<function identity>, info=<function identity>, debug=<function identity>, option=<function identity>, subcommand=<function identity>, choice=<function identity>, metavar=<function identity>, bracket=<function identity>, envvar=<function identity>, default=<function identity>, range_label=<function identity>, required=<function identity>, argument=<function identity>, deprecated=<function identity>, search=<function identity>, success=<function identity>, cross_ref_highlight=True, subheading=<function identity>)¶
Color theme for Click Extra to force no colors.
- click_extra.theme.theme_registry: dict[str, Callable[[], HelpExtraTheme]] = {'dark': <function HelpExtraTheme.dark>, 'light': <function HelpExtraTheme.light>}¶
Registry of named theme factories used by
ThemeOption.Each entry maps a theme name to a zero-argument callable returning a
HelpExtraThemeinstance. Factories (rather than pre-built instances) let consumers register themes that depend on runtime state.Use
register_theme()to add custom themes before the CLI is invoked, sinceThemeOptionbuilds itsclick.Choicefrom this registry at instantiation time.
- click_extra.theme.register_theme(name, factory)[source]¶
Register a named theme factory in
theme_registry.- Parameters:
name (
str) ā Lowercase identifier used as the--themechoice value.factory (
Callable[[],HelpExtraTheme]) ā Zero-argument callable returning aHelpExtraTheme.
- Return type:
- click_extra.theme.KO = '\x1b[31mā\x1b[0m'¶
Pre-rendered UI-elements.
- class click_extra.theme.ThemeOption(param_decls=None, default='dark', is_eager=True, expose_value=False, help='Color theme used for help screens.', **kwargs)[source]¶
Bases:
ExtraOptionA pre-configured option that adds a
--themeflag to select the help-screen color theme.The list of valid theme names is pulled from
theme_registryat instantiation time. Register new themes withregister_theme()before declaring your commands, otherwise they will not appear in the optionās choices.The selected theme is stored on the Click context under
click_extra.context.THEME, so it applies for the duration of the current invocation only and does not leak into sibling invocations sharing the same process.- static set_theme(ctx, param, value)[source]¶
Resolve the chosen theme name and store it on the Click context.
Looks up value in
theme_registry, calls its factory, and writes the resultingHelpExtraThemeunderclick_extra.context.THEMEinctx.meta. Click sharesmetaacross the parent/child context hierarchy, so subcommands inherit the parent groupās pick automatically.- Return type: