Rich turns a plain Python CLI into something readable: coloured text, aligned tables, bordered panels, progress bars, and prompts — all from one library with no terminal-escape-code wrangling on your part. This hub surveys the pieces Rich gives you for building a friendly terminal UI, and points you to the deeper guides for each. It is aimed at anyone who has a working CLI and wants the output to stop looking like a 1990s log dump.
TL;DR
- Everything starts with a
Console. Create one, callconsole.print(...), and you get markup, colour, and word-wrapping for free. - Rich ships ready-made building blocks:
Table,Panel, styled text,Prompt/Confirm, andProgressfor progress bars and spinners. - Rich detects when output is not a terminal (piped, redirected, in CI) and degrades gracefully — it drops control codes and respects
NO_COLORso your tool stays scriptable.
The Console: one object for all output
The Console is the entry point for everything Rich draws. Replace print() with console.print() and you immediately get console markup, automatic word-wrap to the terminal width, and theme-aware colour.
from rich.console import Console
console = Console()
console.print("[bold green]✓ deploy succeeded[/] in 4.2s")
console.print({"region": "eu-west-1", "replicas": 3}) # pretty-prints structures
Create the Console once and pass it around (or stash it on a small app object) rather than constructing a new one per call. That single instance is also what keeps progress bars, prompts, and log lines from fighting over the cursor — a theme that comes up again on the progress bars and spinners page.
Tables and panels for structured output
When your command returns rows — servers, files, test results — reach for Table. For a framed summary or a callout, use Panel.
from rich.table import Table
from rich.panel import Panel
table = Table(title="Servers")
table.add_column("Host")
table.add_column("Status", style="green")
table.add_row("web-01", "up")
table.add_row("web-02", "down")
console.print(table)
console.print(Panel("All systems nominal", title="Status"))
Tables handle column widths, alignment, and wrapping automatically, so you never hand-pad strings again.
Styled text and prompts
Rich markup ([bold], [red], [link=…]) inlines styling without escape codes. For interactive input, rich.prompt gives you typed, validated prompts:
from rich.prompt import Prompt, Confirm
name = Prompt.ask("Project name", default="my-cli")
if Confirm.ask("Initialise a git repo?", default=True):
...
Prompt.ask supports choices=[...], default values, and password masking. It is a quick win for interactive flows, though for anything driven by flags or files you will still lean on argument parsing and config files and env vars.
Progress, spinners, and live output
Long-running work deserves feedback. Rich's Progress covers deterministic tasks (a known total — copying N files), indeterminate work (a spinner while you wait on a network call), and several concurrent task bars at once. Live lets you re-render a region of the screen in place for dashboards. This is the single biggest UX upgrade for most CLIs, so it has its own deep dive:
- Progress bars and spinners for Python CLIs — deterministic vs. indeterminate tasks, custom columns, multiple bars, and how to log without corrupting the display.
Degrade gracefully when output is not a terminal
This is the rule that separates a polished CLI from a broken one: your tool will be piped into grep, redirected to a file, and run in CI. Rich handles this for you, but only if you let it own the output.
Console auto-detects whether it is attached to a TTY via console.is_terminal. When it is not (a pipe, a file, a CI log), Rich suppresses animations and colour and emits plain text, and it honours the NO_COLOR environment variable. Check is_terminal yourself when you want an explicit fallback:
if console.is_terminal:
console.print(table) # full styled output
else:
for row in rows:
console.print("\t".join(row)) # script-friendly plain text
Set force_terminal=True/False on the Console to override detection in tests or CI, and use Console(record=True) to capture rendered output for regression tests.
Related
- Advanced Input Parsing for Python CLIs — the parent pillar covering input and UX.
- Progress bars and spinners for Python CLIs — the deep dive on
rich.progress. - Handling config files and env vars in CLIs — feed your Rich-powered commands their settings.