cheat sheet

rich

Make terminal output beautiful with Rich. Covers styled print, Console, tables, progress bars, syntax highlighting, live displays, and the rich-cli command.

rich — Beautiful Terminal Output

What it is

Rich is a Python library for rich text and beautiful formatting in the terminal. It provides:

  • Styled and colored print() with markup
  • Tables, panels, and trees
  • Syntax-highlighted code blocks
  • Progress bars (with ETA and throughput)
  • Live-updating displays (dashboards, spinners)
  • Tracebacks with local variable values

It also ships a standalone CLI tool (rich) for piping colored output from the shell.

Output blocks below show ANSI-stripped plain text. In your actual terminal, Rich renders in full color with box-drawing characters and smooth animations.

Install

bash
pip install rich           # Python library
pip install rich-cli       # optional standalone CLI tool

Output: (none — exits 0 on success)

Styled print with markup

Rich overrides the built-in print with a version that interprets [style]text[/style] markup tags, converting them to ANSI escape codes in the terminal. Tags can be nested and combined ([bold red]); escape a literal bracket with \[ or disable markup entirely by passing markup=False to avoid conflicts with text that contains square brackets.

python
from rich import print

print("[bold green]Success![/bold green] Task completed in [cyan]0.42s[/cyan].")
print("[red]Error:[/red] File not found: [italic yellow]config.yaml[/italic yellow]")
print("[bold]Numbers:[/bold] [blue]42[/blue], [magenta]3.14[/magenta], [white on red] WARNING [/white on red]")

Output:

text
Success! Task completed in 0.42s.
Error: File not found: config.yaml
Numbers: 42, 3.14,  WARNING 

In the terminal, "Success!" appears bold green, "0.42s" in cyan, "Error:" in red, and " WARNING " as white text on a red background.

Console — control over output stream

Console() is the central object for all Rich output — it wraps a file-like stream (stdout by default) and tracks terminal width, color support, and encoding. Pass stderr=True to write to stderr, file=open(...) to log to a file, or highlight=False to suppress automatic syntax detection when printing raw data.

python
from rich.console import Console

console = Console()
console.print("Hello from Rich!", style="bold magenta")
console.print("Rendered to stderr:", style="dim", end="\n", highlight=False)
console.rule("[bold]Section Break[/bold]")
console.log("This is a log message — includes timestamp and caller")

Output:

text
Hello from Rich!
Rendered to stderr:
──────────────────────────── Section Break ────────────────────────────
[14:30:01] This is a log message — includes timestamp and caller      rich_demo.py:7

Tables

Table renders data in a Unicode box-drawing grid with configurable column styles, alignment, and padding. Add columns with add_column() before adding any rows; the box parameter (e.g. box=rich.box.SIMPLE) controls the border style, and show_lines=True draws horizontal rules between rows.

python
from rich.console import Console
from rich.table import Table

console = Console()

table = Table(title="Python Package Summary", show_lines=True)
table.add_column("Package", style="cyan", no_wrap=True)
table.add_column("Purpose", style="white")
table.add_column("Install", style="green")

table.add_row("requests", "HTTP client",       "pip install requests")
table.add_row("pandas",   "DataFrames",         "pip install pandas")
table.add_row("fastapi",  "ASGI web framework", "pip install fastapi")
table.add_row("pytest",   "Testing framework",  "pip install pytest")
table.add_row("ruff",     "Linter + formatter", "pip install ruff")

console.print(table)

Output:

text
              Python Package Summary               
┌──────────┬───────────────────┬──────────────────────┐
│ Package  │ Purpose           │ Install              │
├──────────┼───────────────────┼──────────────────────┤
│ requests │ HTTP client       │ pip install requests │
│ pandas   │ DataFrames        │ pip install pandas   │
│ fastapi  │ ASGI web framework│ pip install fastapi  │
│ pytest   │ Testing framework │ pip install pytest   │
│ ruff     │ Linter + formatter│ pip install ruff     │
└──────────┴───────────────────┴──────────────────────┘

Progress bar

Progress is a composable progress display that combines multiple column types (spinner, bar, percentage, ETA, throughput) into a single live-updating line. Use it as a context manager; call add_task() to register a task with a total count, then advance(task_id) or update(task_id, completed=n) as work proceeds. Multiple concurrent tasks can be tracked in the same Progress instance.

python
import time
from rich.progress import Progress, SpinnerColumn, TimeElapsedColumn

items = list(range(20))

with Progress(
    SpinnerColumn(),
    "[progress.description]{task.description}",
    "[progress.percentage]{task.percentage:>3.0f}%",
    TimeElapsedColumn(),
) as progress:
    task = progress.add_task("Processing items...", total=len(items))
    for item in items:
        time.sleep(0.05)  # simulate work
        progress.advance(task)

print("Done!")

Output:

text
⠸ Processing items... 100%  0:00:01
Done!

During execution the progress bar animates with a spinner, live percentage, and elapsed time. The final state shows 100%.

Track (simple progress)

For simple iterables, rich.progress.track is the quickest way:

python
import time
from rich.progress import track

results = []
for i in track(range(10), description="Computing..."):
    time.sleep(0.1)
    results.append(i * i)

print(results)

Output:

text
Computing... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:01
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Syntax highlighting

Syntax wraps a code string with a language name and a Pygments theme, rendering it with token-level color highlighting when printed via a Console. Pass line_numbers=True to show gutter line numbers and highlight_lines={3, 5} to draw attention to specific lines.

python
from rich.console import Console
from rich.syntax import Syntax

console = Console()

code = '''
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))
'''

syntax = Syntax(code.strip(), "python", theme="monokai", line_numbers=True)
console.print(syntax)

Output:

text
  1 def fibonacci(n: int) -> int:
  2     if n < 2:
  3         return n
  4     return fibonacci(n - 1) + fibonacci(n - 2)
  5 
  6 print(fibonacci(10))

In the terminal, keywords appear in purple, strings in yellow, and line numbers are dimmed — Monokai theme colors.

Live updating display

Live holds a renderable (table, panel, text, or any Rich object) and refreshes it in-place at a set rate without scrolling the terminal. Use it for dashboards, download monitors, or any output that replaces itself rather than appending; call live.update(new_renderable) to swap the content each frame.

python
import time
from rich.console import Console
from rich.live import Live
from rich.table import Table

console = Console()

def make_table(step: int) -> Table:
    table = Table(title=f"Live Update — step {step}")
    table.add_column("Metric")
    table.add_column("Value", style="cyan")
    table.add_row("Step",     str(step))
    table.add_row("Progress", f"{step * 10}%")
    table.add_row("Rate",     f"{step * 1.7:.1f} items/s")
    return table

with Live(make_table(0), refresh_per_second=4) as live:
    for i in range(1, 11):
        time.sleep(0.3)
        live.update(make_table(i))

console.print("[bold green]Finished![/bold green]")

Output (final frame):

text
     Live Update — step 10     
┌──────────┬────────────────┐
│ Metric   │ Value          │
├──────────┼────────────────┤
│ Step     │ 10             │
│ Progress │ 100%           │
│ Rate     │ 17.0 items/s   │
└──────────┴────────────────┘
Finished!

Pretty-print data structures

console.print() automatically detects dicts, lists, and other Python objects and renders them with indentation and syntax coloring, similar to pprint but prettier. For more control, call rich.pretty.pprint(obj) directly, or inspect(obj) to get a formatted panel showing an object's attributes, methods, and docstrings.

python
from rich import inspect, pretty
from rich.console import Console

console = Console()

data = {
    "users": [
        {"id": 1, "name": "Alice", "roles": ["admin", "user"]},
        {"id": 2, "name": "Bob",   "roles": ["user"]},
    ],
    "total": 2,
}
console.print(data)

Output:

text
{
    'users': [
        {'id': 1, 'name': 'Alice', 'roles': ['admin', 'user']},
        {'id': 2, 'name': 'Bob', 'roles': ['user']}
    ],
    'total': 2
}

Rich tracebacks

install() replaces Python's default sys.excepthook with Rich's traceback renderer, which highlights source lines, color-codes the exception chain, and — when show_locals=True — displays local variable values at each frame. Call it once at program startup; it has no effect on normal execution paths.

python
from rich.traceback import install
install(show_locals=True)  # call once at app startup

# Now all unhandled exceptions produce rich tracebacks
# with syntax-highlighted frames and local variable values
raise ValueError("something went wrong")

Output:

text
╭─────────────────── Traceback (most recent call last) ───────────────────╮
│ demo.py:5 in <module>                                                    │
│                                                                          │
│   3 install(show_locals=True)                                            │
│   4                                                                      │
│ ❱ 5 raise ValueError("something went wrong")                            │
│                                                                          │
╰──────────────────────────────────────────────────────────────────────────╯
ValueError: something went wrong

rich-cli — shell command

The rich CLI tool lets you pipe and render content from the shell:

bash
# Render markdown in the terminal
rich README.md

# Syntax-highlight a Python file
rich main.py --syntax python

# Pretty-print JSON
cat data.json | rich - --json

# Render a CSV as a table
rich data.csv

Output (markdown rendering):

text
                       My Project                        
                                                         
  A short description of the project.                   
                                                         
  Usage                                                  
  ━━━━━                                                  
                                                         
  ┌──────────────────────────────────────────────────┐  
  │ $ python main.py --help                          │  
  └──────────────────────────────────────────────────┘  

Markup reference

MarkupEffect
[bold]text[/bold]Bold
[italic]text[/italic]Italic
[underline]text[/underline]Underline
[red]text[/red]Red foreground
[on blue]text[/on blue]Blue background
[bold cyan]text[/bold cyan]Bold cyan
[link=https://example.com]click[/link]Hyperlink (terminals that support OSC 8)
[/]Reset all styles

Themes and named styles

A Theme is a mapping from style names to Style objects, plugged into a Console so you can refer to styles by name ([error]oops[/error]) instead of repeating low-level markup. Themes are the right way to enforce a consistent visual vocabulary across a CLI — success, warning, error, subtle, kbd — without scattering color literals throughout the codebase.

python
from rich.console import Console
from rich.theme import Theme

theme = Theme({
    "info":    "dim cyan",
    "warning": "yellow",
    "error":   "bold red",
    "success": "bold green",
    "kbd":     "white on grey23",
})
console = Console(theme=theme)

console.print("[info]Loading config…[/info]")
console.print("[warning]Cache miss[/warning]")
console.print("[error]Connection refused[/error]")
console.print("Press [kbd] Ctrl+C [/kbd] to abort", highlight=False)

Output (semantic colors applied):

text
Loading config…
Cache miss
Connection refused
Press  Ctrl+C  to abort

Load a theme from a .ini file with Theme.read("mytheme.ini") — useful for shipping multiple looks (dark/light) with a CLI tool.

Panel — bordered boxes

Panel(renderable, title=..., border_style=..., padding=...) wraps any Rich object in a box with an optional title. Panels are the building block for dashboards — group related information visually without committing to a full table.

python
from rich.console import Console
from rich.panel import Panel
from rich.text import Text

console = Console()

console.print(Panel("Hello from Rich!", title="Greeting", border_style="cyan"))

body = Text.from_markup(
    "[bold]Status:[/bold] [green]OK[/green]\n"
    "[bold]Uptime:[/bold] 3 days\n"
    "[bold]Load:[/bold] 0.42 0.38 0.31"
)
console.print(Panel(body, title="System", subtitle="myhost",
                     border_style="green", padding=(1, 2)))

Output:

text
╭────── Greeting ──────╮
│ Hello from Rich!     │
╰──────────────────────╯
╭───────── System ─────────╮
│                          │
│   Status: OK             │
│   Uptime: 3 days         │
│   Load:   0.42 0.38 0.31 │
│                          │
╰────────────────── myhost ╯

Tree — hierarchical data

Tree(label) builds a renderable tree where each node can be a string, a Rich object, or another Tree. Use tree.add(node) to attach children. Trees render with Unicode box-drawing connectors and respect the parent Console's width.

python
from rich.console import Console
from rich.tree import Tree

tree = Tree("[bold]project[/bold]")
src = tree.add("src/")
src.add("app.py")
src.add("utils.py")
sub = src.add("sub/")
sub.add("a.py")
sub.add("b.py")
tree.add("tests/").add("test_app.py")
tree.add("README.md")
tree.add(".env [dim](hidden)[/dim]")

Console().print(tree)

Output:

text
project
├── src/
│   ├── app.py
│   ├── utils.py
│   └── sub/
│       ├── a.py
│       └── b.py
├── tests/
│   └── test_app.py
├── README.md
└── .env (hidden)

Markdown rendering

Markdown(text) renders CommonMark in the terminal — headings, lists, code blocks, blockquotes, tables — using box-drawing characters and syntax-highlighted fences. Useful for printing READMEs, embedded help, or LLM responses inside a Python CLI.

python
from rich.console import Console
from rich.markdown import Markdown

md = """
# Project

A short description of the project.

## Usage

```python
import click
@click.command()
def hello():
    click.echo("hi")
```

- Bullet 1
- Bullet 2
- Bullet 3

> Note: this is a blockquote.
"""

Console().print(Markdown(md))

Output:

text
                                Project

 A short description of the project.


 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 ┃ Usage                                                                   ┃
 ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

 │ import click
 │ @click.command()
 │ def hello():
 │     click.echo("hi")

  • Bullet 1
  • Bullet 2
  • Bullet 3

  ▌Note: this is a blockquote.

Multi-task progress bars

Progress can track several concurrent tasks in the same display — pass each one to add_task() and update independently. The renderer interleaves rows in the order they were added.

python
import time
from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn

with Progress(
    TextColumn("[progress.description]{task.description}"),
    BarColumn(bar_width=40),
    "[progress.percentage]{task.percentage:>3.0f}%",
    TimeRemainingColumn(),
) as progress:
    download = progress.add_task("[cyan]Downloading…", total=100)
    extract  = progress.add_task("[magenta]Extracting…", total=100, start=False)
    install  = progress.add_task("[green]Installing…",  total=100, start=False)

    for step in range(100):
        progress.update(download, advance=1)
        if step == 20:
            progress.start_task(extract)
        if step > 20:
            progress.update(extract, advance=1.2)
        if step == 60:
            progress.start_task(install)
        if step > 60:
            progress.update(install, advance=2.4)
        time.sleep(0.03)

The available columns are: SpinnerColumn, BarColumn, TextColumn (uses task.description/task.percentage/task.completed/task.total), TaskProgressColumn, TimeElapsedColumn, TimeRemainingColumn, MofNCompleteColumn, FileSizeColumn, TotalFileSizeColumn, DownloadColumn, TransferSpeedColumn. Mix and match to taste.

File-download progress

For real-world downloads, combine Progress with urlopen (or httpx) and update by bytes received. DownloadColumn and TransferSpeedColumn format the values nicely.

python
import urllib.request
from rich.progress import (
    Progress, BarColumn, DownloadColumn, TransferSpeedColumn, TimeRemainingColumn,
)

url = "https://example.com/large.zip"

with Progress(
    "[progress.description]{task.description}",
    BarColumn(),
    DownloadColumn(),
    TransferSpeedColumn(),
    TimeRemainingColumn(),
) as progress:
    response = urllib.request.urlopen(url)
    total = int(response.headers.get("Content-Length", 0))
    task = progress.add_task("Downloading", total=total)
    with open("download.zip", "wb") as f:
        chunk = response.read(8192)
        while chunk:
            f.write(chunk)
            progress.update(task, advance=len(chunk))
            chunk = response.read(8192)

Status — indeterminate spinner

console.status(message) is the simplest way to show "I'm doing something" when you can't measure progress. It uses a context manager and disappears cleanly when the block exits. Pass spinner="dots" (default), "line", "earth", "moon", "clock", etc. for different animations.

python
import time
from rich.console import Console

console = Console()

with console.status("[bold green]Loading dataset…", spinner="dots"):
    time.sleep(2)
console.print("[green]✓[/green] Loaded")

with console.status("Building index…", spinner="earth"):
    time.sleep(1.5)
console.print("[green]✓[/green] Indexed")

Output (after completion):

text
✓ Loaded
✓ Indexed

Run python -m rich.spinner to view every available spinner animation side-by-side.

Layout — splitting the screen

Layout() divides the terminal into regions that can be updated independently — useful for top-style dashboards. Combine with Live for a refreshing dashboard.

python
import time
from datetime import datetime
from rich.console import Console
from rich.layout import Layout
from rich.live import Live
from rich.panel import Panel
from rich.text import Text

layout = Layout()
layout.split_column(
    Layout(name="header", size=3),
    Layout(name="body"),
    Layout(name="footer", size=3),
)
layout["body"].split_row(
    Layout(name="left"),
    Layout(name="right"),
)

def make_clock():
    return Panel(Text(datetime.now().strftime("%H:%M:%S"), justify="center"),
                  title="Clock", border_style="cyan")

def make_load(rate):
    return Panel(f"items/s: {rate:.1f}", title="Throughput", border_style="green")

with Live(layout, refresh_per_second=4, screen=False) as live:
    for i in range(20):
        layout["header"].update(Panel("[bold]Dashboard[/bold]", border_style="magenta"))
        layout["left"].update(make_clock())
        layout["right"].update(make_load(i * 1.7))
        layout["footer"].update(Panel("Press Ctrl+C to quit", border_style="dim"))
        time.sleep(0.25)

Prompt — interactive input

rich.prompt provides type-coerced and validated input helpers — Prompt.ask, IntPrompt.ask, FloatPrompt.ask, Confirm.ask. Each accepts an optional default, choices, and password=True flag. The styled prompt text and clear error messages give a much better UX than bare input().

python
from rich.prompt import Prompt, IntPrompt, Confirm, FloatPrompt

name  = Prompt.ask("[cyan]What is your name?[/]", default="alice")
age   = IntPrompt.ask("Age", default=30)
ratio = FloatPrompt.ask("Threshold", default=0.5)
env   = Prompt.ask("Environment", choices=["dev", "staging", "prod"],
                    default="dev")
pw    = Prompt.ask("Password", password=True)

if Confirm.ask("Save these settings?"):
    print(f"Saved: name={name} age={age} ratio={ratio} env={env}")

Output:

text
What is your name? (alice): bob
Age (30): 42
Threshold (0.5): 0.9
Environment [dev/staging/prod] (dev): prod
Password:
Save these settings? [y/n] (y): y
Saved: name=bob age=42 ratio=0.9 env=prod

Columns — flowing layout

Columns(items, equal=True, expand=True) lays out a sequence of renderables in newspaper-style columns that wrap based on terminal width. Useful for showing a long list of short items without manually computing rows.

python
from rich.console import Console
from rich.columns import Columns
from rich.panel import Panel

console = Console()
panels = [Panel(f"item {i}", border_style="cyan") for i in range(12)]
console.print(Columns(panels, equal=True, expand=True))

Output:

text
╭ item 0 ╮ ╭ item 1 ╮ ╭ item 2 ╮ ╭ item 3 ╮
│        │ │        │ │        │ │        │
╰────────╯ ╰────────╯ ╰────────╯ ╰────────╯
╭ item 4 ╮ ╭ item 5 ╮ ╭ item 6 ╮ ╭ item 7 ╮
│        │ │        │ │        │ │        │
╰────────╯ ╰────────╯ ╰────────╯ ╰────────╯
…

Align and Padding

Align(renderable, "center"/"left"/"right") centres or aligns child output inside the parent width. Padding(renderable, (top, right, bottom, left)) adds whitespace around any object. Combine to centre a panel or add breathing room around dense output.

python
from rich.align import Align
from rich.console import Console
from rich.padding import Padding
from rich.panel import Panel

console = Console()
console.print(Align(Panel("Centered", border_style="magenta"), "center"))
console.print(Padding(Panel("Padded by 2", border_style="cyan"), (2, 4)))

Logging integration

RichHandler is a logging.Handler that routes Python's logging records through Rich — formatted timestamps, colored levels, tracebacks with locals, and clickable file paths. Configure once at startup and every logger.info("…") call across your codebase becomes a Rich log line.

python
import logging
from rich.logging import RichHandler

logging.basicConfig(
    level=logging.INFO,
    format="%(message)s",
    datefmt="[%X]",
    handlers=[RichHandler(rich_tracebacks=True, markup=True, show_path=False)],
)

log = logging.getLogger("myapp")
log.info("server started on port 8080")
log.warning("cache miss for [cyan]user:123[/]")
log.error("connection refused", extra={"markup": True})

try:
    1 / 0
except Exception:
    log.exception("computation failed")  # full rich traceback

Output:

text
[14:32:01] INFO     server started on port 8080
[14:32:01] WARNING  cache miss for user:123
[14:32:01] ERROR    connection refused
[14:32:01] ERROR    computation failed
                    ╭──── Traceback (most recent call last) ────╮
                    │ ZeroDivisionError: division by zero       │
                    ╰───────────────────────────────────────────╯

Capturing and recording output

console.capture() captures all rendered output as a string instead of writing to the terminal — useful for testing or re-emitting Rich content into logs. console.record=True enables console.save_html(...) and console.save_svg(...) to export the session to HTML or SVG.

python
from rich.console import Console
from rich.table import Table

console = Console(record=True, width=60)

table = Table(title="Demo")
table.add_column("Name");  table.add_column("Value")
table.add_row("Alice", "42");  table.add_row("Bob", "17")
console.print(table)

# Capture to string
with console.capture() as cap:
    console.print("[red]error[/red]")
s = cap.get()
print(repr(s))

# Save the whole session as HTML or SVG
console.save_html("session.html", inline_styles=True)
console.save_svg("session.svg", title="My Run")

Pager — page long output

console.pager() redirects output through the system pager (less on Unix, more on Windows) just like git log. Useful for showing long help, paginated tables, or a long log of operations.

python
from rich.console import Console

console = Console()

with console.pager(styles=True):
    for i in range(500):
        console.print(f"[cyan]{i:04d}[/cyan]  line content here")

Pass styles=True to keep colors in the pager — less needs -R for ANSI, which Rich sets automatically via LESS=-R.

Inspect — discover an object's API

inspect(obj, methods=True, help=True) renders a Rich panel showing an object's attributes, method signatures, and docstrings. It is the modern, color-coded replacement for dir(obj) / help(obj).

python
from rich import inspect
from pathlib import Path

inspect(Path("/tmp"), methods=True, help=True, sort=True)

Output: (renders as a multi-section panel listing every method and attribute on Path with type signatures and short docstrings.)

ANSI detection and output to non-terminals

Console detects whether stdout is a TTY and disables colors automatically when piped to a file or another process — python myscript.py > log.txt produces clean text, not ANSI gibberish. Override with force_terminal=True (always color) or no_color=True (never color); set the NO_COLOR env var to disable globally (the no-color.org standard).

python
from rich.console import Console

# Auto-detect (default)
c = Console()

# Force colors even when piped
c2 = Console(force_terminal=True)

# Inhibit colors
c3 = Console(no_color=True)

# File output: no ANSI, plain text
with open("output.txt", "w") as f:
    Console(file=f).print("[red]stripped on write[/red]")

# Pin width (helpful in CI to keep snapshots stable)
c4 = Console(width=80)

Comparison with tqdm, prompt_toolkit, and colorama

Rich's progress bars overlap with tqdm; its prompts overlap with prompt_toolkit; its color output overlaps with colorama. The right tool depends on the use case.

NeedUse
Simple iteration progress, minimal depstqdm
Multi-task, columnar progress, file-download UXrich.progress
Interactive forms, autocomplete, mouse supportprompt_toolkit
Type-coerced one-shot prompts (ask, confirm)rich.prompt
Cross-platform ANSI on Windowscolorama (or any modern Windows terminal — Rich handles this on its own with colorama as an optional dep)
Full-screen TUI appstextual (sister project by the same author)
Log formattingRichHandler

Rich is the right answer for output (tables, syntax highlighting, dashboards, logs); reach for prompt_toolkit or textual when you need a full keyboard-driven UI; reach for tqdm when you want one tiny dependency for a single progress bar.

Common pitfalls

Square brackets in user data break markup. Printing "[INFO]" literally triggers Rich to look for a tag. Either escape with \[ ("\\[INFO]") or pass markup=False to disable interpretation for that print: console.print(user_input, markup=False).

Auto-highlighting can corrupt data. Rich colorizes numbers, URLs, UUIDs, file paths automatically — fine for human output, bad for round-tripping. Disable with highlight=False: console.print(data, highlight=False).

Live only allows one instance per Console. Two with Live(...) blocks on the same console at once will crash. Pass console=Console() or refactor to a single Live with composite content.

Progress doesn't auto-advance in async loops. Each iteration must call progress.advance(task) or progress.update(task, completed=...). Use track() for synchronous iterables to skip the bookkeeping.

install() replaces sys.excepthook globally. Call it once, at the entry point of your app — not from a library. In libraries, never call install() on import.

Width detection lies in CI. When Rich runs in a pipe or without a TTY, console.width defaults to 80. Pin it explicitly (Console(width=120)) when generating fixed-width snapshots for documentation or test assertions.

Real-world recipes

A polished CLI status report

Combine Panel, Table, and a small theme for a "status dashboard" that a deploy script prints at the end. This is the standard Rich pattern for replacing 30 lines of print() statements with a single 10-line block.

python
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.theme import Theme

theme = Theme({"ok": "bold green", "fail": "bold red", "skip": "yellow"})
console = Console(theme=theme)

results = [
    ("build",       "ok",   "2.3s"),
    ("unit tests",  "ok",   "12.1s"),
    ("integration", "fail", "8.4s"),
    ("deploy",      "skip", "—"),
]

table = Table(show_header=True, header_style="bold")
table.add_column("Step")
table.add_column("Status", justify="center")
table.add_column("Time",   justify="right")
for name, status, dt in results:
    table.add_row(name, f"[{status}]{status.upper()}[/]", dt)

failed = sum(1 for _, s, _ in results if s == "fail")
summary = "[ok]All green[/ok]" if failed == 0 else f"[fail]{failed} failed[/fail]"

console.print(Panel(table, title="CI Report", subtitle=summary,
                     border_style="cyan", padding=(1, 2)))

Combined progress and logging

When work is loud (one log per step) and long (a progress bar), interleaving the two is essential — Rich does it cleanly because Progress redraws below the log output.

python
import logging
import time
from rich.logging import RichHandler
from rich.progress import Progress

logging.basicConfig(level=logging.INFO, format="%(message)s",
                     handlers=[RichHandler(show_path=False)])
log = logging.getLogger("worker")

items = ["alpha", "bravo", "charlie", "delta", "echo"]

with Progress(transient=True) as progress:
    task = progress.add_task("Processing", total=len(items))
    for name in items:
        log.info(f"working on {name}")
        time.sleep(0.4)
        progress.advance(task)

log.info("done")

transient=True removes the bar after completion, leaving only the log lines.

Render a tree of the current directory

A pure-Rich tree clone — useful both as a tool and as a worked example of recursion + Tree.add.

python
from pathlib import Path
from rich.console import Console
from rich.tree import Tree

def walk(node: Tree, path: Path, depth=0, max_depth=3):
    if depth >= max_depth:
        return
    for child in sorted(path.iterdir(), key=lambda p: (p.is_file(), p.name)):
        if child.name.startswith("."):
            continue
        label = f"[bold cyan]{child.name}/[/]" if child.is_dir() else child.name
        sub = node.add(label)
        if child.is_dir():
            walk(sub, child, depth + 1, max_depth)

root = Path.cwd()
tree = Tree(f"[bold]{root}[/bold]")
walk(tree, root)
Console().print(tree)