Rich renders beautiful formatted output in the terminal with zero dependencies. pip install rich. from rich import print, from rich.console import Console. Print markup: print("[bold green]Success![/]"), print("[red]Error:[/] file not found"). Console: console = Console(), console.print("[blue]Info[/]", highlight=True). Log: console.log("Processing", extra={"file": "data.csv"}). Rule: console.rule("[yellow]Section Header[/]"). Table: from rich.table import Table, t = Table(title="Results", border_style="blue"), t.add_column("Name", style="cyan", no_wrap=True), t.add_column("Score", justify="right"), t.add_row("Alice", "98.5"), console.print(t). Progress: from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, with Progress(...) as p: task = p.add_task("Downloading", total=100); p.advance(task, 10). Panel: from rich.panel import Panel, console.print(Panel("[green]Done[/]", title="Status", border_style="green")). Syntax: from rich.syntax import Syntax, console.print(Syntax(code, "python", theme="monokai", line_numbers=True)). Markdown: from rich.markdown import Markdown, console.print(Markdown("## Hello\n- item\n- item")). Inspect: from rich import inspect, inspect(obj, methods=True). Tree: from rich.tree import Tree, tree = Tree("Root"), tree.add("Child 1").add("Grandchild"). Live: from rich.live import Live, with Live(console=console) as live: live.update(table). Traceback: from rich.traceback import install, install(show_locals=True). Logging: import logging; from rich.logging import RichHandler; logging.basicConfig(handlers=[RichHandler()]). Claude Code generates Rich terminal dashboards, progress-tracked pipelines, pretty report tables, and structured log handlers.
CLAUDE.md for Rich
## Rich Stack
- Version: rich >= 13.0
- Print: from rich import print — drop-in replacement with markup
- Console: Console(highlight=True, log_time=True) — reuse one instance
- Table: Table(title, border_style) → add_column(style, justify) → add_row
- Progress: Progress(SpinnerColumn(), BarColumn(), ...) as p: p.add_task/advance
- Panel/Syntax/Markdown: Panel(text, title) | Syntax(code, "python", theme)
- Live: Live(renderable, console=console) for dynamic updates
- Logging: basicConfig(handlers=[RichHandler(rich_tracebacks=True)])
Rich Terminal Output Pipeline
# cli/rich_pipeline.py — beautiful terminal output with Rich
from __future__ import annotations
import logging
import time
import traceback
from pathlib import Path
from typing import Any, Generator
from rich import print as rprint
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.syntax import Syntax
from rich.markdown import Markdown
from rich.progress import (
Progress, SpinnerColumn, BarColumn,
TextColumn, TimeElapsedColumn, TimeRemainingColumn,
MofNCompleteColumn, TaskProgressColumn,
)
from rich.live import Live
from rich.tree import Tree
from rich.columns import Columns
from rich.text import Text
from rich.rule import Rule
from rich.layout import Layout
from rich.logging import RichHandler
from rich import traceback as rich_tb
from rich import inspect as rich_inspect
from rich.theme import Theme
from rich.style import Style
# ── 0. Global console ─────────────────────────────────────────────────────────
# Custom theme
THEME = Theme({
"success": "bold green",
"warning": "bold yellow",
"error": "bold red",
"info": "bold cyan",
"heading": "bold white",
"dim_text": "dim white",
})
console = Console(theme=THEME, highlight=True)
# ── 1. Basic output ───────────────────────────────────────────────────────────
def print_status(message: str, status: str = "info") -> None:
"""Print a status message with icon prefix."""
icons = {"success": "✅", "warning": "⚠️ ", "error": "❌", "info": "ℹ️ "}
icon = icons.get(status, "·")
console.print(f"{icon} [{status}]{message}[/]")
def print_section(title: str, width: int = 60) -> None:
"""Print a styled section divider."""
console.rule(f"[heading]{title}[/]", style="blue")
def print_panel(
content: str,
title: str = "",
status: str = "info",
expand: bool = False,
) -> None:
"""Display content in a bordered panel."""
border_styles = {"success": "green", "warning": "yellow",
"error": "red", "info": "blue"}
border = border_styles.get(status, "blue")
console.print(Panel(content, title=f"[bold]{title}[/]",
border_style=border, expand=expand))
def print_code(
code: str,
language: str = "python",
title: str = "",
theme: str = "monokai",
numbers: bool = True,
) -> None:
"""Display syntax-highlighted code block."""
syntax = Syntax(code, language, theme=theme, line_numbers=numbers)
if title:
console.print(Panel(syntax, title=f"[bold]{title}[/]"))
else:
console.print(syntax)
def print_markdown(text: str) -> None:
"""Render Markdown in the terminal."""
console.print(Markdown(text))
# ── 2. Tables ─────────────────────────────────────────────────────────────────
def make_table(
title: str,
columns: list[dict], # [{"header": "Col", "style": "cyan", "justify": "left"}, ...]
rows: list[list],
border: str = "rounded", # "rounded" | "heavy" | "double" | "simple" | "minimal"
show_footer: bool = False,
caption: str = "",
) -> Table:
"""
Build a Rich table.
columns: list of {"header": str, "style": str, "justify": str, "no_wrap": bool}
rows: list of lists (values may be str or Rich renderable)
"""
table = Table(
title=title,
box=_get_box(border),
show_footer=show_footer,
caption=caption,
header_style="bold magenta",
border_style="bright_black",
)
for col in columns:
table.add_column(
col["header"],
style=col.get("style", ""),
justify=col.get("justify", "left"),
no_wrap=col.get("no_wrap", False),
footer=col.get("footer", ""),
)
for row in rows:
table.add_row(*[str(v) if not hasattr(v, "__rich_console__") else v for v in row])
return table
def _get_box(style: str):
from rich import box
return {
"rounded": box.ROUNDED,
"heavy": box.HEAVY,
"double": box.DOUBLE,
"simple": box.SIMPLE,
"minimal": box.MINIMAL,
"markdown": box.MARKDOWN,
}.get(style, box.ROUNDED)
def print_dataframe(
df, # pandas DataFrame
title: str = "",
max_rows: int = 20,
border: str = "rounded",
) -> None:
"""Display a Pandas DataFrame as a Rich table."""
cols = [{"header": str(c), "style": "cyan" if i == 0 else "", "justify": "left"}
for i, c in enumerate(df.columns)]
rows = df.head(max_rows).values.tolist()
rows = [[str(v) for v in row] for row in rows]
if len(df) > max_rows:
rows.append([f"[dim]... {len(df) - max_rows} more rows ...[/]"] + [""] * (len(df.columns) - 1))
table = make_table(title or "DataFrame", cols, rows, border=border)
console.print(table)
# ── 3. Progress bars ──────────────────────────────────────────────────────────
def tracked_progress(
items: list,
label: str = "Processing",
show_remaining: bool = True,
) -> Generator:
"""
Context manager that yields (item, progress_object, task_id).
Use to wrap any iterable with a Rich progress bar.
"""
columns = [
SpinnerColumn(),
TextColumn("[bold blue]{task.description}"),
BarColumn(bar_width=None),
TaskProgressColumn(),
MofNCompleteColumn(),
]
if show_remaining:
columns.append(TimeRemainingColumn())
columns.append(TimeElapsedColumn())
with Progress(*columns, console=console, expand=False) as progress:
task = progress.add_task(label, total=len(items))
for item in items:
yield item
progress.advance(task, 1)
def multi_task_progress(
tasks: list[dict], # [{"label": str, "total": int, "fn": callable}, ...]
) -> dict[str, Any]:
"""
Run multiple tasks concurrently showing individual progress bars.
tasks: [{"label": "Download A", "total": 100, "fn": lambda p, tid: ...}]
Returns dict of results keyed by label.
"""
columns = [
SpinnerColumn(),
TextColumn("{task.description:<30}"),
BarColumn(),
TaskProgressColumn(),
TimeElapsedColumn(),
]
results = {}
with Progress(*columns, console=console) as progress:
task_ids = {t["label"]: progress.add_task(t["label"], total=t["total"])
for t in tasks}
for t in tasks:
results[t["label"]] = t["fn"](progress, task_ids[t["label"]])
return results
# ── 4. Tree display ───────────────────────────────────────────────────────────
def dict_to_tree(data: Any, name: str = "Root") -> Tree:
"""
Recursively convert a dict/list to a Rich Tree for display.
Great for showing JSON config, nested objects, or directory structure.
"""
tree = Tree(f"[bold]{name}[/]")
_add_tree_nodes(tree, data)
return tree
def _add_tree_nodes(parent, data: Any) -> None:
if isinstance(data, dict):
for key, value in data.items():
if isinstance(value, (dict, list)):
branch = parent.add(f"[cyan]{key}[/]")
_add_tree_nodes(branch, value)
else:
parent.add(f"[cyan]{key}:[/] [yellow]{value!r}[/]")
elif isinstance(data, list):
for i, item in enumerate(data[:20]):
if isinstance(item, (dict, list)):
branch = parent.add(f"[dim][{i}][/]")
_add_tree_nodes(branch, item)
else:
parent.add(f"[dim][{i}][/] [yellow]{item!r}[/]")
if len(data) > 20:
parent.add(f"[dim]... {len(data) - 20} more items[/]")
else:
parent.add(f"[yellow]{data!r}[/]")
def path_tree(root: Path, max_depth: int = 3) -> Tree:
"""Display a directory tree."""
tree = Tree(f"[bold]{root}[/]", guide_style="blue")
def _walk(node: Tree, path: Path, depth: int) -> None:
if depth > max_depth:
return
for item in sorted(path.iterdir()):
if item.is_dir():
branch = node.add(f"[bold blue]📁 {item.name}/[/]")
_walk(branch, item, depth + 1)
else:
size = item.stat().st_size
node.add(f"[green]📄 {item.name}[/] [dim]({size:,} bytes)[/]")
if root.exists():
_walk(tree, root, 1)
return tree
# ── 5. Live dashboard ─────────────────────────────────────────────────────────
def live_counter(
total: int,
label: str = "Processing",
delay: float = 0.05,
) -> None:
"""Example: animated live counter in a panel."""
with Live(console=console, refresh_per_second=20) as live:
for i in range(total + 1):
bar_len = 30
filled = int(bar_len * i / total)
bar = "█" * filled + "░" * (bar_len - filled)
pct = 100 * i // total
content = Text()
content.append(f"\n {label}\n\n", style="bold")
content.append(f" [{bar}] {pct}% {i}/{total}\n", style="green")
live.update(Panel(content, title="Progress", border_style="blue",
width=50, expand=False))
time.sleep(delay)
# ── 6. Logging integration ────────────────────────────────────────────────────
def setup_rich_logging(level: int = logging.INFO) -> None:
"""
Replace the default logging handler with RichHandler.
Call once at app startup for beautiful log output.
"""
rich_tb.install(show_locals=True) # Pretty tracebacks globally
logging.basicConfig(
level=level,
format="%(message)s",
datefmt="[%X]",
handlers=[
RichHandler(
console=console,
rich_tracebacks=True,
tracebacks_suppress=[],
markup=True,
show_path=True,
)
],
)
# ── Demo ──────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print_section("Rich Terminal Output Demo")
# Status messages
print_status("Connection established", "success")
print_status("Cache miss — fetching fresh data", "warning")
print_status("Invalid API key", "error")
print_status("Processing 1,234 records", "info")
console.print()
print_section("Table Demo")
columns = [
{"header": "Model", "style": "bold cyan", "no_wrap": True},
{"header": "AUC", "style": "", "justify": "right"},
{"header": "F1", "style": "", "justify": "right"},
{"header": "Latency", "style": "yellow", "justify": "right"},
]
rows = [
["CatBoost", "0.9234", "0.871", "12 ms"],
["XGBoost", "0.9187", "0.864", "8 ms"],
["LightGBM", "0.9201", "0.867", "6 ms"],
["LogisticReg", "0.8811", "0.823", "1 ms"],
]
table = make_table("Model Comparison", columns, rows)
console.print(table)
console.print()
print_section("Syntax Highlight Demo")
print_code(
'@app.command()\ndef process(file: Path = typer.Argument(...)):\n console.print("[green]Done[/]")',
language="python", title="CLI handler"
)
console.print()
print_section("Tree Demo")
config = {
"database": {"host": "localhost", "port": 5432},
"cache": {"backend": "redis", "ttl": 3600},
"features": ["auth", "logging", "metrics"],
}
console.print(dict_to_tree(config, "app_config"))
console.print()
print_section("Progress Demo")
items = list(range(30))
for item in tracked_progress(items, label="Training"):
time.sleep(0.02)
print_status("All demos complete!", "success")
For the print() + colorama alternative — colorama requires manual ANSI code insertion while Rich’s markup syntax [bold red]text[/] is readable in source code, auto-strips on non-TTY output (pipes, logs), and Console(file=sys.stderr) cleanly separates progress output from stdout, making pipelines composable. For the tqdm alternative for progress bars — tqdm tracks iteration count while Rich’s Progress composites arbitrary columns (spinner, bar, percentage, ETA, file sizes), the Live context renders dynamic tables and multi-line dashboards that update in-place without scroll, and RichHandler replaces the logging stack with no configuration beyond one basicConfig call, giving logging, tables, syntax highlighting, and progress bars from a single library. The Claude Skills 360 bundle includes Rich skill sets covering print markup and secho-style output, Console with custom themes, Table with custom column styles, Progress with multi-column trackers, Live dynamic dashboard, Tree for nested structures, Panel and Syntax code blocks, Markdown rendering, dict_to_tree for config display, path tree, and RichHandler logging setup. Start with the free tier to try terminal UI code generation.