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

Dark background

light

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. ThemeOption does not reassign it: per-invocation choices live on ctx.meta instead. click_extra.wrap.patch_click() does reassign it to override the fallback for the entire patched session.

  • nocolor_theme: an all-identity theme 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 light with --no-color emits no ANSI codes.

  • --theme dark with --color (the default) emits the dark theme’s ANSI codes.

  • NO_COLOR=1 in the environment overrides any --theme choice 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: HelpTheme

Extends cloup.HelpTheme with logging.levels and extra properties.

critical()¶
Return type:

TypeVar(T)

error()¶
Return type:

TypeVar(T)

warning()¶
Return type:

TypeVar(T)

info()¶
Return type:

TypeVar(T)

debug()¶

Log levels from Python’s logging module.

Return type:

TypeVar(T)

option()¶
Return type:

TypeVar(T)

subcommand()¶
Return type:

TypeVar(T)

choice()¶
Return type:

TypeVar(T)

metavar()¶
Return type:

TypeVar(T)

bracket()¶
Return type:

TypeVar(T)

envvar()¶
Return type:

TypeVar(T)

default()¶
Return type:

TypeVar(T)

range_label()¶
Return type:

TypeVar(T)

required()¶
Return type:

TypeVar(T)

argument()¶
Return type:

TypeVar(T)

deprecated()¶
Return type:

TypeVar(T)

search()¶
Return type:

TypeVar(T)

success()¶

Click Extra new coloring properties.

Return type:

TypeVar(T)

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:

HelpExtraTheme

static dark()[source]¶

A theme assuming a dark terminal background color.

Return type:

HelpExtraTheme

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_white becomes black, and cyan accents become blue since cyan on white is hard to read.

Return type:

HelpExtraTheme

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. ThemeOption writes its picked theme to ctx.meta rather 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_click is 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:

  1. The theme stored on the active Click context under click_extra.context.THEME (set by ThemeOption from --theme).

  2. The module-level default_theme (the dark default, or whatever click_extra.wrap.patch_click() set at process start).

Falling back through the active context (instead of reading a module attribute) keeps --theme scoped to the invocation that received it, so a second invocation in the same process starts from the default again.

Return type:

HelpExtraTheme

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 HelpExtraTheme instance. 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, since ThemeOption builds its click.Choice from this registry at instantiation time.

click_extra.theme.register_theme(name, factory)[source]¶

Register a named theme factory in theme_registry.

Parameters:
Return type:

None

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: ExtraOption

A pre-configured option that adds a --theme flag to select the help-screen color theme.

The list of valid theme names is pulled from theme_registry at instantiation time. Register new themes with register_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 resulting HelpExtraTheme under click_extra.context.THEME in ctx.meta. Click shares meta across the parent/child context hierarchy, so subcommands inherit the parent group’s pick automatically.

Return type:

None