Python Rich Prompts and CLI Output: A Practical Guide
python rich prompts and cli output: Learn to build interactive prompts and styled terminal output with Python Rich, including validation, tables, and progress bars.
python rich prompts and cli output requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you need to build a command-line tool that asks for user input and prints structured results, plain print() and input() quickly become limiting. The Rich library gives you styled output, interactive prompts, tables, and progress bars without heavy dependencies. This article focuses on practical patterns for using Rich to handle prompts and CLI output in Python, from basic console styling to validated input and dynamic displays.
Setting Up Rich and the Console Object
Rich is a third-party package, so you first install it:
pip install rich
The central object is Console. It handles rendering, color, and width detection. Create one instance and reuse it across your application rather than creating a new one for every call:
from rich.console import Console console = Console()
Console has a print() method that accepts the same arguments as the built-in print() but adds styling. It also respects terminal width and supports markup strings for inline formatting.
Styling Terminal Output with Rich
Rich uses markup syntax to apply styles without separate calls. For example:
console.print("[bold green]Success[/bold green] - file saved") console.print("[red]Error:[/red] invalid value")
You can also pass a style parameter to print():
console.print("Processing complete", style="bold cyan")
For dynamic values, use f-strings with markup carefully. If the value contains characters like [ or ], they can break the markup parser. Use rich.markup.escape or pass the value as a separate argument to print():
from rich.markup import escape user_input = "[not markup]" console.print(f"You entered: {escape(user_input)}")
This prevents accidental style injection and keeps output predictable.
Building Interactive Prompts with Prompt.ask
Rich provides Prompt.ask() to collect input with a styled prompt and optional validation. The simplest usage:
from rich.prompt import Prompt name = Prompt.ask("What is your name?") console.print(f"Hello, {name}")
Prompt.ask() returns a string. You can specify a default value and a password flag:
port = Prompt.ask("Port", default="8080") api_key = Prompt.ask("API key", password=True)
The password=True option hides the input as it is typed, which is useful for secrets.
Validating User Input in Prompts
Prompt.ask() accepts a validator callable that receives the raw input string and returns a boolean. If validation fails, it re-prompts automatically. You can also use choices to restrict input to a set of options:
from rich.prompt import Prompt choice = Prompt.ask( "Select mode", choices=["fast", "safe", "debug"], default="safe" )
For numeric input, use Prompt.ask with a custom validator or use IntPrompt and FloatPrompt from the same module:
from rich.prompt import IntPrompt count = IntPrompt.ask("How many items?", default=10)
These specialized prompts return typed values and handle conversion errors gracefully.
Rendering Tables and Panels for Structured Output
When you need to display tabular data, Rich's Table class keeps columns aligned and supports styling per column or row:
from rich.table import Table table = Table(title="Build Results") table.add_column("Service", style="cyan") table.add_column("Status", style="green") table.add_row("api", "ok") table.add_row("worker", "failed") console.print(table)
For grouping related output, Panel adds a border and optional title:
from rich.panel import Panel console.print(Panel("Deployment complete", title="Result", border_style="blue"))
These components compose well with prompts. For example, you can prompt for a filter, then render a table of matching records.
Progress Bars for Long-Running Tasks
Rich's Progress class gives you a live progress bar with minimal setup. It is useful when your CLI performs network calls or file processing after a prompt:
from rich.progress import Progress import time with Progress() as progress: task = progress.add_task("Uploading...", total=100) for i in range(100): time.sleep(0.01) progress.update(task, advance=1)
You can add multiple tasks and customize the display columns. The progress bar renders in place and clears when done, which keeps the terminal clean.
Performance and Maintainability Considerations
Rich adds overhead compared to plain print(). For very high-volume output (e.g., logging thousands of lines per second), styling can slow down the process. In those cases, conditionally disable Rich or use Console(quiet=True) to suppress output entirely. For interactive tools, the overhead is negligible.
From a maintainability perspective, keep all Rich components in one module or a small set of helper functions. Avoid sprinkling console.print() calls with complex markup throughout business logic. Instead, centralize output formatting so that changing the style later does not require touching every call site.
Also be aware of terminal compatibility. Rich handles most modern terminals, but some features (like true color) may not render on older setups. The library falls back to a compatible mode, but you can test by setting FORCE_COLOR=1 or using Console(color_system="standard") to force a specific palette.
Combining Prompts and Output in a Complete Flow
A typical CLI flow collects parameters, validates them, performs work, and displays results. The following example combines a prompt with choices, a progress bar, and a table:
from rich.console import Console from rich.prompt import Prompt from rich.progress import Progress from rich.table import Table import time console = Console() mode = Prompt.ask("Mode", choices=["build", "test", "deploy"], default="build") console.print(f"Starting {mode}...") with Progress() as progress: task = progress.add_task("Processing", total=50) for i in range(50): time.sleep(0.02) progress.update(task, advance=1) results = Table(title="Summary") results.add_column("Step", style="cyan") results.add_column("Status", style="green") results.add_row("Validation", "ok") results.add_row(mode.capitalize(), "done") console.print(results)
This pattern scales well: the prompt gathers the decision, the progress bar gives feedback during the work, and the table presents the outcome in a readable format. You can extend the same structure to handle errors by wrapping the work in a try/except and printing a styled error message before exiting with a non-zero code.