Skip to content

Panels - Overview

rich-click lets you configure grouping and sorting of command options and subcommands. The containers which contain grouped options and subcommands are called panels:

By default, RichCommands have a single panel for options named "Options", and RichGroups have an additional panel for commands named "Commands".

rich-click allows you to control and customize everything about these panels:

  • The default panels can be renamed and stylized.
  • Options and commands can be split up across multiple panels.
  • Positional arguments can be given a separate panel, or included in options panels.
  • The styles of these panels can be modified.

Info

Panels are a replacement of rich-click "groups," which are deprecated as of version 1.9.0. We will support the old groups API for the foreseeable future, but its use is discouraged.

Although groups technically can be combined with panels, doing so can lead to unpredictable sorting behavior.

You can read the rich-click v1.8 docs to learn more about groups API.

Introduction to API

The high-level API for defining panels is with the @click.command_panel() and @click.option_panel() decorators.

Under the hood, these decorators create RichPanel objects that are attached to the command.

Options

Options panels handle parameters for your command:

# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click

@click.command()
@click.option("--src", help="Source")
@click.option("--dest", help="Destination")
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
@click.version_option("1.2.3")
@click.option_panel("Main",
                    options=["--src", "--dest"])
@click.option_panel("Extra",
                    options=["--env", "--log-level", "--help", "--version"])
@click.rich_config({"style_options_panel_border": "dim blue"})
def move_item(src, dest, env, log_level):
    """Move an item from a src location to a dest location"""
    pass

if __name__ == "__main__":
    move_item()
Output

python panels_simple_decorators.py --help

Alternatively, you can configure panels within the option itself. If the panel is not created in a decorator, then one is created on the fly.

The following code generates the same output as the example above:

# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click

@click.command()
@click.option("--src", panel="Main", help="Source")
@click.option("--dest", panel="Main", help="Destination")
@click.option("--env", panel="Extra", help="Environment")
@click.option("--log-level", panel="Extra", help="Log level")
@click.help_option(panel="Extra")
@click.version_option("1.2.3", panel="Extra")
@click.rich_config({"style_options_panel_border": "dim blue"})
def move_item(src, dest, env, log_level):
    """Move an item from a src location to a dest location"""
    pass

if __name__ == "__main__":
    move_item()
Output

Note that this output is the same as the previous example, even though it was defined differently. python panels_simple_kwargs.py --help

Arguments

Despite the name, options panels handle more than just options; they can also handle positional arguments.

Arguments can be given their own panel with the show_arguments config option:

# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click

@click.command()
@click.argument("src")
@click.argument("dest")
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
@click.rich_config({"show_arguments": True})
def move_item(src, dest, env, log_level):
    """Move an item from a src location to a dest location"""
    pass

if __name__ == "__main__":
    move_item()
Output

python panels_simple_arguments.py --help

Arguments can also be included in the options panel with the group_arguments_options config option (the show_arguments config option does not need to be set).

# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click

@click.command()
@click.argument("src")
@click.argument("dest")
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
@click.rich_config({"group_arguments_options": True})
def move_item(src, dest, env, log_level):
    """Move an item from a src location to a dest location"""
    pass

if __name__ == "__main__":
    move_item()
Output

python panels_simple_arguments_combined.py --help

In rich-click, unlike base Click, arguments can have help text. If help= if set for arguments, then the argument panel is automatically shown:

# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click

@click.command()
@click.argument("src", help="Source")
@click.argument("dest", help="Destination")
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
def move_item(src, dest, env, log_level):
    """Move an item from a src location to a dest location"""
    pass

if __name__ == "__main__":
    move_item()
Output

python panels_simple_arguments_help.py --help

Commands

Sub-commands also have panels that are defined similarly to option panels:

# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click

@click.group()
@click.command_panel("Items",
                     commands=["move-item", "update-item"])
@click.command_panel("Users",
                     commands=["create-user", "update-user"],
                     help="User management commands")
def cli():
    """CLI"""
    pass

@cli.command()
def move_item():
    """Move an item"""
    pass

@cli.command()
def update_item():
    """Update an item"""
    pass

@cli.command()
def create_user():
    """Create a user"""
    pass

@cli.command()
def update_user():
    """Update a user"""
    pass

if __name__ == "__main__":
    cli()
Output

python panels_commands.py --help

Styles & Panel Help

RichPanel objects inherit their base style behaviors from the rich config by default, but this can be set on a per-panel basis.

Panels can also have help text.

The below example shows both of these things:

# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click

@click.command()
@click.option("--src")
@click.option("--dest")
@click.option("--env")
@click.option("--log-level")
@click.option_panel("Options",
                    help="All of the options available",
                    help_style="green",
                    panel_styles={"box": "DOUBLE"},
                    table_styles={
                        "row_styles": ["dim on rgb(16,16,32)", "on rgb(32,32,72)"],
                        "caption": "The arguments are optional"
                    })
@click.rich_config({"color_system": "truecolor"})
def move_item(src, dest, env, log_level):
    """Move an item from a src location to a dest location"""
    pass

if __name__ == "__main__":
    move_item()
Output

python panels_extra_kwargs.py --help

The panel_styles is passed into the outer rich.panel.Panel(), and the table_styles dict is pass as kwargs into the inner rich.table.Table().

See the available arguments for the rich library Table and Panel objects for more information:

Overriding defaults

Default panel titles can be overridden with the config. Renamed panels can still have their panel-level configurations modified.

# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click

@click.group()
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
@click.option_panel("Some Additional Options",
                    panel_styles={"border_style": "dim blue"})
@click.command_panel("My Tool's Subcommands",
                     panel_styles={"border_style": "dim magenta"})
@click.rich_config({
    "arguments_panel_title": "Very Important Required Args",
    "options_panel_title": "Some Additional Options",
    "commands_panel_title": "My Tool's Subcommands",
    "show_arguments": True
})
def cli(env, log_level):
    """My CLI"""

@cli.command()
@click.argument("src")
@click.argument("dest")
def move_item(src, dest):
    """Move an item from a src location to a dest location"""
    pass

if __name__ == "__main__":
    cli()
Output

python panels_defaults_renamed.py --help

Note that the rich config passes to subcommands, but panels are defined at the command level. So running move-item --help from the above example will rename the children's panels (because that's set in the parent's config), but it does not pass the panel_styles= to the subcommand:

Output

python panels_defaults_renamed.py move-item --help

Default panel styles are also handled by the config, and will be overridden when conflicting options are defined at the panel level.

# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click

# 'Main' panel: uses default title style "bright_red"
# 'Extra' panel: title style is overridden to use "blue"

@click.command()
@click.argument("src", help="Source", panel="Main")
@click.argument("dest", help="Destination", panel="Main")
@click.option("--env", help="Environment", panel="Extra")
@click.option("--log-level", help="Log level", panel="Extra")
@click.help_option(panel="Extra")
@click.option_panel("Main")
@click.option_panel("Extra", title_style="blue")
@click.rich_config({
    "theme": "plain",
    "style_options_panel_title_style": "bright_red"  # <- default title style
})
def move_item(src, dest, env, log_level):
    """Move an item from a src location to a dest location"""
    pass

if __name__ == "__main__":
    move_item()
Output

python panels_defaults_override_config.py --help