Beginner

Plain print() output gets the job done for debugging, but it tells your users nothing about what is important, what is a warning, and what is an error. When you are building a CLI tool that other people will use — or even just a script you run yourself every day — the difference between monochrome output and a well-structured, colored terminal display is the difference between squinting at walls of text and instantly understanding what happened.

Python Rich is a library that makes beautiful terminal output trivially easy. Install it with pip install rich and you get syntax highlighting, colored text, tables, progress bars, live dashboards, tracebacks, and Markdown rendering — all without learning complex terminal escape code sequences. Rich handles the terminal capability detection and fallback automatically.

In this article we will cover Rich’s Console for styled output, Markup for inline color and formatting, Tables for structured data display, Progress bars for long-running tasks, Panels and layout components, syntax-highlighted code output, and a complete real-world CLI dashboard project. By the end you will have the building blocks for any CLI tool that looks like it was built by a professional.

Python Rich: Quick Example

Here is the fastest way to see what Rich can do — five lines that demonstrate color, markup, and table output:

# quick_rich.py
from rich import print
from rich.table import Table

# Inline markup with [color] tags
print("[bold green]Success![/bold green] File saved to [cyan]/tmp/output.csv[/cyan]")
print("[bold red]Error:[/bold red] Could not connect to database")
print("[yellow]Warning:[/yellow] Config file not found, using defaults")

# Quick table
table = Table("Name", "Score", "Grade")
table.add_row("Alice", "95", "[green]A[/green]")
table.add_row("Bob", "72", "[yellow]C[/yellow]")
table.add_row("Charlie", "88", "[cyan]B+[/cyan]")
print(table)

Output (with terminal colors):

Success! File saved to /tmp/output.csv
Error: Could not connect to database
Warning: Config file not found, using defaults

 Name     Score  Grade 
 Alice    95     A     
 Bob      72     C     
 Charlie  88     B+    

The from rich import print line replaces the built-in print function with Rich’s version, which understands markup tags. From that point forward, any [bold green] or [red] tags in your strings become actual terminal colors — zero configuration required. Want to go deeper? Below we cover every major Rich component.

What Is Rich and What Can It Do?

Rich is a Python library for rendering styled text in the terminal. It works on Windows, macOS, and Linux, and gracefully degrades to plain text when run in environments that do not support color (like log files or CI/CD pipelines).

ComponentWhat It DoesKey Class/Function
ConsoleStyled print with markup, loggingConsole()
TableFormatted, bordered data tablesTable()
ProgressProgress bars for loops and tasksProgress(), track()
PanelBordered boxes around contentPanel()
SyntaxSyntax-highlighted code blocksSyntax()
MarkdownRender Markdown in the terminalMarkdown()
LiveAuto-refreshing live displaysLive()
LoggingColored, structured log handlerRichHandler

The Console object is the foundation of everything. You create one with Console() and call console.print() instead of the built-in print. This gives you the full styling system, error handling separation (stdout vs stderr), and the ability to capture output for testing.

The Console Object and Markup

The Console class is the main entry point for Rich output. Unlike the import-replaced print shortcut, using a Console object gives you more control over where output goes, how errors are formatted, and whether color is enabled:

# console_markup.py
from rich.console import Console

console = Console()

# Basic markup -- [style]text[/style]
console.print("[bold]Bold text[/bold]")
console.print("[italic]Italic text[/italic]")
console.print("[underline]Underlined[/underline]")
console.print("[bold red]Bold red error[/bold red]")
console.print("[green on black]Green on black background[/green on black]")

# Named styles
console.print("Normal text")
console.print("Error text", style="bold red")
console.print("Success text", style="bold green")
console.print("Info text", style="cyan")

# Print to stderr for errors
err_console = Console(stderr=True)
err_console.print("[red]This goes to stderr[/red]")

# Disable color (for log files)
plain_console = Console(no_color=True)
plain_console.print("[bold]This prints without color codes[/bold]")

# Rule -- a horizontal line with optional title
console.rule("[bold blue]Section Title[/bold blue]")
console.print("Content after the rule")
console.rule()

Output:

Bold text
Italic text
Underlined
Bold red error
Green on black background
Normal text
Error text
Success text
Info text
[stderr] This goes to stderr
This prints without color codes
─────────────────────── Section Title ───────────────────────
Content after the rule
─────────────────────────────────────────────────────────────

The console.rule() call creates a full-width horizontal divider — perfect for separating sections in long command output. The separate err_console for stderr is important in CLI tools where stdout is often piped to another command or redirected to a file; error messages should go to stderr so they do not corrupt the stdout stream.

Tables

Rich’s Table class renders properly aligned, bordered tables in the terminal. It handles column alignment, row styles, and nested markup in cell content:

# rich_tables.py
from rich.console import Console
from rich.table import Table

console = Console()

# Build a table with column options
table = Table(title="Server Status", show_header=True, header_style="bold cyan")
table.add_column("Server", style="dim", width=20)
table.add_column("Status", justify="center")
table.add_column("CPU %", justify="right")
table.add_column("Uptime", justify="right")

# Add rows with inline markup
table.add_row("web-01", "[green]Online[/green]", "34%", "14 days")
table.add_row("web-02", "[green]Online[/green]", "67%", "14 days")
table.add_row("db-01",  "[yellow]Degraded[/yellow]", "89%", "3 days")
table.add_row("cache-01","[red]Offline[/red]", "--", "--")

console.print(table)

Output:

                   Server Status                    
 Server      Status      CPU %   Uptime  
 web-01      Online       34%   14 days  
 web-02      Online       67%   14 days  
 db-01       Degraded     89%    3 days  
 cache-01    Offline       --        --  

Column options like justify="right" and width=20 control the layout. The style="dim" on the Server column makes it visually secondary to the Status column, guiding the reader’s eye to the most important information first. Each cell can contain Rich markup, so you can color individual cell values based on their content — green for Healthy, red for down — without any extra formatting logic.

Progress Bars and Live Displays

Rich’s progress tracking is one of its most-used features. The track() function wraps any iterable and shows a progress bar as you iterate through it:

# progress_bars.py
import time
from rich.progress import track, Progress, SpinnerColumn, TimeElapsedColumn
from rich.console import Console

console = Console()

# Simple progress bar with track()
console.print("[bold]Processing files...[/bold]")
files = [f'file_{i}.csv' for i in range(20)]
for f in track(files, description="Processing..."):
    time.sleep(0.1)  # Simulate work

# Advanced progress with multiple columns
console.print("\n[bold]Running tasks...[/bold]")
tasks_data = [("Downloading data", 50), ("Processing records", 200), ("Saving output", 30)]

with Progress(
    SpinnerColumn(),
    "[progress.description]{task.description}",
    "[progress.percentage]{task.percentage:>3.0f}%",
    TimeElapsedColumn(),
) as progress:
    for task_name, total_steps in tasks_data:
        task = progress.add_task(task_name, total=total_steps)
        for _ in range(total_steps):
            time.sleep(0.01)
            progress.advance(task)

console.print("[green]All tasks complete![/green]")

Output (live in terminal):

Processing files...
Processing... [############################] 20/20 0:00:02

Running tasks...
/ Downloading data      100%  0:00:00
/ Processing records    100%  0:00:02
/ Saving output         100%  0:00:00
All tasks complete!

The track() function is ideal for simple loops. The full Progress context manager is better when you have multiple concurrent tasks or want to customize which columns appear in the progress display. Both update in-place without scrolling the terminal — the progress bar stays on the current lines and is replaced by a completion message when done.

Real-Life Example: System Monitoring CLI Dashboard

Here is a complete system monitoring dashboard that combines Console, Table, Panel, and Rule components to display structured status information in a clean, readable format:

# system_cli.py
import time
import random
from datetime import datetime, timedelta
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.columns import Columns
from rich import box

console = Console()

def get_mock_services():
    """Simulate service status data."""
    return [
        {"name": "web-server",   "status": "running", "cpu": 34, "mem": 512, "uptime": "14d 6h"},
        {"name": "database",     "status": "running", "cpu": 22, "mem": 2048,"uptime": "14d 6h"},
        {"name": "cache",        "status": "running", "cpu": 8,  "mem": 256, "uptime": "14d 6h"},
        {"name": "task-queue",   "status": "warning", "cpu": 78, "mem": 1024,"uptime": "2d 1h"},
        {"name": "email-worker", "status": "stopped", "cpu": 0,  "mem": 0,   "uptime": "--"},
    ]

def get_mock_metrics():
    return {
        "total_requests_today": 142857,
        "errors_today": 23,
        "avg_response_ms": 87,
        "active_users": 1247,
    }

def status_color(status):
    colors = {"running": "green", "warning": "yellow", "stopped": "red"}
    return f'[{colors.get(status, "white")}]{status}[/{colors.get(status, "white")}]'

def cpu_color(cpu):
    if cpu > 80: return f"[red]{cpu}%[/red]"
    if cpu > 60: return f"[yellow]{cpu}%[/yellow]"
    return f"[green]{cpu}%[/green]"

def display_dashboard():
    console.clear()
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    # Header
    console.rule(f"[bold cyan]System Dashboard -- {now}[/bold cyan]")
    
    # Services table
    table = Table(title="Services", box=box.ROUNDED, show_header=True, header_style="bold")
    table.add_column("Service", style="dim")
    table.add_column("Status", justify="center")
    table.add_column("CPU", justify="right")
    table.add_column("Memory", justify="right")
    table.add_column("Uptime", justify="right")
    
    for svc in get_mock_services():
        mem_str = f'{svc["mem"]} MB' if svc["mem"] > 0 else "--"
        table.add_row(
            svc["name"],
            status_color(svc["status"]),
            cpu_color(svc["cpu"]),
            mem_str,
            svc["uptime"]
        )
    console.print(table)
    
    # Metrics panels
    metrics = get_mock_metrics()
    panels = [
        Panel(f'[bold cyan]{metrics["total_requests_today"]:,}[/bold cyan]\nRequests today', title="Traffic"),
        Panel(f'[bold green]{metrics["active_users"]:,}[/bold green]\nActive users', title="Users"),
        Panel(f'[bold yellow]{metrics["avg_response_ms"]}ms[/bold yellow]\nAvg response', title="Latency"),
        Panel(f'[bold red]{metrics["errors_today"]}[/bold red]\nErrors today', title="Errors"),
    ]
    console.print(Columns(panels))
    console.rule()

display_dashboard()

Output:

──────────────── System Dashboard -- 2026-05-05 10:15:30 ────────────────

         Services
 Service        Status    CPU   Memory   Uptime 
 web-server     running   34%   512 MB   14d 6h 
 database       running   22%   2048 MB  14d 6h 
 cache          running    8%   256 MB   14d 6h 
 task-queue     warning   78%   1024 MB   2d 1h 
 email-worker   stopped    0%   --           -- 

 Traffic        Users         Latency     Errors  
 142,857        1,247         87ms        23      
 Requests today Active users  Avg response Errors today
──────────────────────────────────────────────────────

The console.clear() at the start makes this work as a refreshing dashboard — call display_dashboard() in a loop with a sleep delay to get a live-updating display. Replace the mock data functions with real psutil calls to get actual system metrics. You can also wrap the whole display in a Live() context for smooth flickerless updates.

Frequently Asked Questions

How do I install Rich?

Run pip install rich. Rich requires Python 3.6.3 or higher and works on Windows, macOS, and Linux. It has no required C extensions — it is pure Python. For the best experience on Windows, use Windows Terminal rather than the legacy Command Prompt, which has limited color support. On most modern terminals (iTerm2, GNOME Terminal, VS Code integrated terminal), Rich works out of the box.

Why are colors not showing up in my terminal?

Rich detects color support from the terminal environment. If you run your script in a context without color support (some CI systems, redirected output, or legacy terminals), Rich falls back to plain text automatically. To force colors on, create the Console with Console(force_terminal=True). To always disable colors, use Console(no_color=True). When piping output to a file (python script.py > output.txt), Rich correctly strips color codes since files do not support ANSI escape sequences.

How do I use Rich for logging?

Replace the default logging handler with Rich’s RichHandler: logging.basicConfig(handlers=[RichHandler()]). This formats log output with colored level labels, timestamps, source file locations, and auto-detected code in log messages. The handler integrates with Python’s standard logging module, so all your existing logger.info() and logger.error() calls get Rich formatting automatically without changing your logging code.

How do I make a live auto-refreshing display?

Use Rich’s Live context manager: wrap your renderable (a Table, Layout, or any Rich object) with Live(renderable, refresh_per_second=4). Update the renderable object inside the loop and call live.update(new_renderable) to refresh the display without screen flicker. This is the building block for tools like htop-style monitors, live log tails, and real-time progress dashboards.

What is rich.inspect and when is it useful?

The rich.inspect(obj) function prints a detailed, colored summary of any Python object — its class, attributes, methods, and docstrings. It is more useful than dir(obj) because it shows values alongside names and filters out dunder methods by default. Use it during development to explore unfamiliar library objects: from rich import inspect; inspect(my_object). It works on modules, instances, functions, and built-in types.

Conclusion

Rich transforms terminal output from a debugging afterthought into a first-class communication layer. We covered the Console object for styled printing, Markup for inline color and formatting tags, Tables for structured data, Progress for tracking long-running operations, Panels for boxed content, and the Live component for refreshing dashboards.

The system monitoring dashboard is a solid template for any operations or data CLI tool. Extend it by adding keyboard interaction with rich.prompt, markdown report generation with rich.markdown, or a file viewer with rich.syntax. The pattern of combining multiple Rich components into a single coherent display scales to tools as complex as full terminal IDEs.

See the official Rich documentation for the complete API reference and example gallery.