pyguides

Rich Terminal Output in Python

The Python standard library gives you plain text. When you want colored output, tables, or progress indicators in your terminal, the rich library is the standard choice. It works on Linux, macOS, and Windows, and requires no external dependencies.

Installing Rich

pip install rich

Verify it works:

python -m rich

You should see a colored demonstration of Rich’s capabilities. If you are on Windows, use Windows Terminal for the best experience — the legacy cmd.exe does not support ANSI escape codes.

Rich.print — Styled Output Without Setup

Rich replaces Python’s built-in print with a drop-in replacement that understands markup tags:

from rich import print

print("[bold red]Error:[/bold red] something went wrong")
print("[green]Success![/green] Operation complete")
print("[yellow]Warning:[/yellow] disk space low")

The [bold red] tags apply styling until closed with [/bold red]. You can combine styles:

print("[bold italic yellow on red blink]This text is loud[/]")

Styles apply until you close them. Use [/] to close all open styles:

print("[bold]Bold start[italic] bold and italic [/italic] back to bold [/] plain text")

Available Colors and Styles

Colors: black, red, green, yellow, blue, magenta, cyan, white

Attributes: bold, italic, underline, strike, blink, reverse

Background colors: on black, on red, on green, etc.

Console Markup

Rich’s markup syntax is inspired by BBCode. You can use it in any string that Rich renders:

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

console = Console()

table = Table(title="Server Status")
table.add_column("Host", style="cyan")
table.add_column("Status", style="green")

table.add_row("web-01", "[green]healthy[/green]")
table.add_row("web-02", "[red]down[/red]")
console.print(table)

Escaping User Input

If your output includes user-generated content, escape it to prevent markup injection:

from rich.markup import escape
from rich.console import Console

console = Console()

username = "[bold]admin[/bold]"  # malicious input
console.print(f"Welcome, {escape(username)}!")
# Output: Welcome, [bold]admin[/bold] (literal, not bold)

Without escaping, a user could inject styles into your terminal output — for example, clearing the screen or triggering invisible sequences.

Tables

rich.table.Table builds aligned, bordered tables:

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

console = Console()

table = Table(title="Package Versions")
table.add_column("Package", justify="left", style="cyan")
table.add_column("Version", justify="right", style="magenta")
table.add_column("Status", justify="center")

table.add_row("requests", "2.31.0", "[green]OK[/green]")
table.add_row("rich", "13.7.0", "[green]OK[/green]")
table.add_row("click", "8.1.7", "[yellow]update[/yellow]")

console.print(table)

The justify argument controls alignment per column. Rich automatically wraps long content and respects terminal width.

Syntax Highlighting

For code output, use Syntax with a lexer name:

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

console = Console()

code = """
def greet(name: str) -> str:
    return f"Hello, {name}!"

result = greet("world")
print(result)
"""

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

Rich supports 300+ languages via the pygments library. Available themes include monokai, dracula, github-dark, nord, and many others.

Progress Bars

Long-running operations need progress feedback. Use Progress for simple cases:

import time
from rich.console import Console
from rich.progress import Progress

console = Console()

with Progress() as progress:
    task = progress.add_task("[cyan]Downloading...", total=100)
    while not progress.finished:
        progress.update(task, advance=1)
        time.sleep(0.05)

For concurrent tasks, track wraps a simple iteration:

from rich.progress import track
import time

for step in track(range(10), description="Processing..."):
    time.sleep(0.2)

Multiple concurrent progress bars:

from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn

console = Console()

with Progress(
    TextColumn("[cyan]{task.description}"),
    BarColumn(),
    TextColumn("{task.completed}/{task.total}"),
    TimeRemainingColumn(),
    console=console,
) as progress:
    t1 = progress.add_task("Download", total=100)
    t2 = progress.add_task("Process", total=100)

    for i in range(100):
        progress.update(t1, advance=1)
        time.sleep(0.05)
        if i % 3 == 0:
            progress.update(t2, advance=1)
            time.sleep(0.05)

Status Spinners

Show that something is happening without a progress bar:

from rich.console import Console
import time

console = Console()

with console.status("[bold green]Connecting to server...") as status:
    time.sleep(2)
    status.update("[bold yellow]Fetching data...")
    time.sleep(2)
    console.print("[green]Connected![/green]")

The spinner automatically adjusts to your terminal width and disappears when the block exits.

Live Display Updates

For dynamic content that changes in place, use Live:

from rich.console import Console
from rich.live import Live
import time

console = Console()

with Live(console=console, refresh_per_second=10) as live:
    for i in range(1, 51):
        live.update(f"[cyan]Processing batch {i}/50[/cyan]")
        time.sleep(0.1)

Live is useful for building dashboards or status displays that update in the terminal without scrolling.

Logging with Rich

Rich can colorize your log messages and provide styled tracebacks:

import logging
from rich.console import Console
from rich.logging import RichHandler

logging.basicConfig(
    level="NOTSET",
    format="%(message)s",
    handlers=[RichHandler(console=Console(), rich_tracebacks=True)]
)

log = logging.getLogger("myapp")
log.warning("This is a warning")
log.error("Something failed")

rich_tracebacks=True gives you styled tracebacks with syntax highlighting for exceptions.

Panel and Group

Panel wraps content in a bordered box:

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

console = Console()

text = Text("Deploy complete!\n3 services updated.", style="green")
console.print(Panel(text, title="[bold]Deployment Status[/bold]", border_style="green"))

Group arranges multiple renderables:

from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.group import Group

console = Console()

table = Table()
table.add_column("Metric")
table.add_column("Value")
table.add_row("CPU", "23%")
table.add_row("Memory", "1.2 GB")

stats = Panel("Server is healthy", border_style="green")
console.print(Group(stats, table))

When Not to Use Rich

Rich requires a terminal that supports ANSI escape codes. If your output goes to a file, log, or pipe, plain text is better:

import sys
from rich import print

if not sys.stdout.isatty():
    print("Progress: 50%")  # plain text for logs

Rich automatically disables styling when output is redirected, so most of the time you do not need to worry about this.

Common Issues

Styles do not appear on Windows. Use Windows Terminal instead of the legacy cmd.exe. Rich will tell you if it detects an unsupported terminal.

Markup appearing literally in output. You are printing user input without escaping it. Use rich.markup.escape() on untrusted strings.

Progress bar not updating in a real application. Pass the Progress instance to any function that updates it from different threads or contexts. Each update call must be thread-safe.

See Also