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
- /tutorials/cli-click-framework/ — build CLIs with the Click framework
- /guides/argparse-guide/ — parse command-line arguments with argparse
- /guides/rich-terminal/ — the reference guide for Rich’s core features