Your Python script runs fine, but the output is plain white text on a black background, you can’t tell how far along it is, and error logs get mixed in with regular output? I’ve been there. An automation project that started at 200 lines had ballooned to 2,000 — and one of the hardest lessons I learned was: ugly terminal output made me spend twice as long debugging.
Rich was built to solve exactly that problem. The library now gets over 50 million downloads per month on PyPI — and that’s no accident. In just 10 minutes, your terminal will look completely different.
Up and Running in 5 Minutes
Install:
pip install rich
Quick test — run this command to check if your terminal supports colors:
python -m rich
If you see a colorful output, you’re good to go. Now replace print() with Rich:
from rich import print
from rich.console import Console
console = Console()
# Print with color, bold, italic — using HTML-like markup
console.print("[bold green]✓ Deploy successful![/bold green]")
console.print("[red]✗ Database connection error[/red]")
console.print("[yellow]⚠ Warning: disk usage 85%[/yellow]")
Try running it — green, red, and yellow will appear immediately. The next sections cover the features I use every day in real scripts.
Data Tables — No More Manual Column Alignment
I used to use f"{col1:<20}{col2:<15}" for column alignment. It looked bad and broke with long data. Rich Table handles it cleanly:
from rich.console import Console
from rich.table import Table
console = Console()
table = Table(title="Server Status", show_header=True, header_style="bold cyan")
table.add_column("Server", style="dim", width=20)
table.add_column("CPU %", justify="right", style="green")
table.add_column("RAM %", justify="right", style="yellow")
table.add_column("Status", justify="center")
table.add_row("web-01.prod", "23%", "67%", "[green]● Online[/green]")
table.add_row("web-02.prod", "45%", "82%", "[green]● Online[/green]")
table.add_row("db-01.prod", "78%", "91%", "[red]⚠ High Load[/red]")
table.add_row("backup-01", "5%", "34%", "[yellow]○ Standby[/yellow]")
console.print(table)
Table automatically aligns columns, draws borders, and wraps text when the terminal is narrow. I use this pattern in all my monitoring scripts — instead of staring at raw JSON, I now have a clean, readable table.
Dynamic Colors Based on Conditions
def cpu_style(value: int) -> str:
if value > 80:
return f"[bold red]{value}%[/bold red]"
elif value > 60:
return f"[yellow]{value}%[/yellow]"
return f"[green]{value}%[/green]"
table.add_row("web-01", cpu_style(23), "67%", "[green]● Online[/green]")
table.add_row("db-01", cpu_style(91), "91%", "[red]⚠ High Load[/red]")
Progress Bar — Know Exactly Where Your Script Is
Running a batch script without a progress bar means you have no idea if it’s still running or completely hung. I once waited almost 20 minutes before realizing the script had crashed at the start — the CPU had dropped to 0% and I thought it was still processing in the background.
from rich import print
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeRemainingColumn
import time
files = [f"file_{i}.log" for i in range(50)]
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
TimeRemainingColumn(),
) as progress:
task = progress.add_task("Processing log files...", total=len(files))
for file in files:
# Process actual file here
time.sleep(0.05) # Simulate work
progress.update(task, advance=1, description=f"Processing: {file}")
print("[bold green]✓ Done![/bold green]")
Rich automatically calculates speed based on recent progress and gives a fairly accurate time estimate. Running a 10 GB migration while knowing it’ll finish in about 3 minutes is a completely different experience from staring blankly at the screen.
Running Multiple Tasks in Parallel
with Progress() as progress:
task_download = progress.add_task("Download...", total=100)
task_process = progress.add_task("Process...", total=200)
task_upload = progress.add_task("Upload...", total=50)
while not progress.finished:
progress.update(task_download, advance=0.9)
progress.update(task_process, advance=1.1)
progress.update(task_upload, advance=0.5)
time.sleep(0.02)
Syntax Highlighting — Debug Code Directly in the Terminal
Debugging an API response by staring at raw, single-color JSON is a great way to miss fields. I use Syntax to highlight config files, SQL queries, and JSON responses when needed — finding issues is much faster:
from rich.syntax import Syntax
from rich.console import Console
console = Console()
# Highlight Python code
python_code = '''
def check_server_health(host: str, port: int = 80) -> dict:
import socket
try:
sock = socket.create_connection((host, port), timeout=3)
sock.close()
return {"status": "online", "host": host}
except socket.timeout:
return {"status": "timeout", "host": host}
except ConnectionRefusedError:
return {"status": "offline", "host": host}
'''
syntax = Syntax(python_code, "python", theme="monokai", line_numbers=True)
console.print(syntax)
# Highlight JSON — very handy for debugging API responses
import json
from rich.syntax import Syntax
api_response = {
"status": "success",
"data": {"user_id": 42, "role": "admin"},
"timestamp": "2026-11-15T09:30:00Z"
}
json_str = json.dumps(api_response, indent=2, ensure_ascii=False)
syntax = Syntax(json_str, "json", theme="github-dark")
console.print(syntax)
Most commonly used themes: monokai (dark background, easy on the eyes), github-dark (looks like VS Code), dracula, one-dark. Run python -m rich.syntax to see more options.
Advanced: Panel, Tree, and Live Display
These three components aren’t needed every day, but when you do need them, nothing else comes close.
Panel — Frame Important Information
Instead of an error message buried in hundreds of log lines, Panel frames it prominently — impossible to miss:
from rich.panel import Panel
from rich.text import Text
# Prominent error alert — doesn't get lost in regular log output
error_msg = Text()
error_msg.append("ConnectionError: ", style="bold red")
error_msg.append("Cannot connect to db-01.prod:5432\n", style="red")
error_msg.append("Check: firewall rules, pg_hba.conf, network", style="yellow")
console.print(Panel(error_msg, title="[bold red]✗ Critical Error[/bold red]", border_style="red"))
Tree — Display Directory Structure or Dependencies
Visualize server topology or infrastructure structure — far clearer than printing line by line:
from rich.tree import Tree
tree = Tree("[bold]production[/bold] 🖥")
web = tree.add("[green]web servers[/green]")
web.add("web-01 (nginx 1.24, PHP 8.2)")
web.add("web-02 (nginx 1.24, PHP 8.2)")
db = tree.add("[blue]databases[/blue]")
db.add("db-master (PostgreSQL 16)")
db.add("db-replica (PostgreSQL 16, lag: 0.1s)")
tree.add("[yellow]load balancer[/yellow] (HAProxy 2.8)")
console.print(tree)
Live — Real-time Updates Without Clearing the Screen
Update a table in real time without clearing the screen — use this when you need a dashboard to continuously monitor metrics:
from rich.live import Live
from rich.table import Table
import time
def generate_metrics_table(tick: int) -> Table:
table = Table(title=f"Server Metrics — Refresh #{tick}")
table.add_column("Server")
table.add_column("CPU")
table.add_column("Connections")
import random
for i in range(1, 4):
cpu = random.randint(10, 95)
style = "red" if cpu > 80 else "green"
table.add_row(f"web-0{i}", f"[{style}]{cpu}%[/{style}]", str(random.randint(50, 500)))
return table
with Live(generate_metrics_table(0), refresh_per_second=1) as live:
for tick in range(20):
time.sleep(1)
live.update(generate_metrics_table(tick))
Practical Tips from a Real Project
An automation project that grew from 200 to 2,000 lines — here’s what I learned about using Rich correctly:
- Create the Console once, use it everywhere — Don’t instantiate
Console()multiple times. Create it in a dedicated module (console.py), then import it wherever needed. - Log files don’t need color — Use
Console(file=log_file, highlight=False)to write logs without ANSI codes. The log file will be readable withcatwithout garbled characters. - Color is disabled automatically when piping output — Rich detects when output is piped (
python script.py | grep error) and disables colors automatically. No manual handling needed. - Integrates with Python’s standard logging — Rich provides a
RichHandlerthat plugs into Python’sloggingmodule:
import logging
from rich.logging import RichHandler
logging.basicConfig(
level=logging.INFO,
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(rich_tracebacks=True)]
)
log = logging.getLogger("rich")
log.info("Server started")
log.warning("Disk usage high: 87%")
log.error("Database connection failed")
- Rich Traceback — Add this at the top of your script for much nicer, more readable stack traces:
from rich.traceback import install
install(show_locals=True) # show_locals=True prints variable values at the point of failure
Rich adds under 1ms of overhead per print — nothing to worry about. Use it for server monitoring scripts, CI pipelines, data migration tools — debugging is significantly faster when output has color and structure. Just remember one thing: don’t add color for the sake of it. Green for success, red for error, yellow for warning — that’s enough.

