Skip to main content

Rich Output: How to Format Beautiful CLI Text & Tables

Plain text CLIs feel dated. The Rich library makes CLI output professional and readable with colors, tables, panels, and formatted text. Rich integrates seamlessly with Typer and outputs beautiful, responsive formatting that adapts to terminal width automatically.

What is Rich and Why Use It?

Rich is a Python library for rendering rich text and beautiful formatting in terminals. According to the Rich GitHub repository (2026), it's used by tools like Typer itself, pip, and dozens of popular CLI tools. Rich handles Unicode, colors, layouts, and automatically falls back to plain text on terminals that don't support formatting, ensuring compatibility everywhere.

Installing Rich

Install Rich alongside Typer:

pip install rich

Rich is a standalone library that works with any Python program. It requires no additional setup and outputs formatting compatible with most terminals.

Colored Text Output

Add colors to your CLI output to emphasize important information:

import typer
from rich import print

app = typer.Typer()

@app.command()
def deploy(service: str):
"""Deploy a service."""
print(f"[green]✓[/green] Deploying {service}")
print(f"[yellow]⚠[/yellow] This will interrupt service briefly")
print(f"[red]✗[/red] Rollback available if deployment fails")

if __name__ == "__main__":
app()

Rich uses markup tags like [green]...[/green] for colors. Available colors include red, green, yellow, blue, magenta, cyan, and many more. This output is readable and conveys meaning at a glance.

Building Tables

Tables organize data beautifully:

import typer
from rich.table import Table
from rich import print

app = typer.Typer()

@app.command()
def list_services():
"""List all services and their status."""
table = Table(title="Services")

table.add_column("Service", style="cyan")
table.add_column("Status", style="magenta")
table.add_column("CPU %", justify="right", style="green")

table.add_row("web-api", "Running", "23.5")
table.add_row("worker-1", "Running", "15.2")
table.add_row("worker-2", "Stopped", "0.0")

print(table)

if __name__ == "__main__":
app()

Output will be a nicely formatted table with colors and alignment. Rich handles column widths automatically based on terminal size.

Panels and Alerts

Highlight important information with panels:

import typer
from rich.panel import Panel
from rich.console import Console

console = Console()
app = typer.Typer()

@app.command()
def backup_status():
"""Show backup status."""
console.print(
Panel(
"[green]Backup completed successfully[/green]\n"
"Size: 2.4 GB\n"
"Duration: 4m 32s",
title="Backup Report",
border_style="green",
)
)

console.print(
Panel(
"[red]This database is 6 months old[/red]\n"
"Consider running refresh_data --full",
title="Warning",
border_style="red",
)
)

if __name__ == "__main__":
app()

Panels draw a border around content, making alerts stand out. border_style controls the color of the border.

Formatted Output with Syntax Highlighting

Display code or formatted text with syntax highlighting:

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

console = Console()
app = typer.Typer()

@app.command()
def show_config():
"""Display configuration file."""
config = {
"api_url": "https://api.example.com",
"timeout": 30,
"retries": 3,
}

syntax = Syntax(
json.dumps(config, indent=2),
"json",
theme="monokai",
line_numbers=True,
)
console.print(syntax)

if __name__ == "__main__":
app()

Syntax highlighting makes config files, logs, and code snippets readable in the terminal. Rich supports dozens of languages and color themes.

Progress Tracking

Show progress for long-running operations:

import typer
from rich.progress import Progress
import time

app = typer.Typer()

@app.command()
def process_files(count: int = 100):
"""Process multiple files."""
with Progress() as progress:
task = progress.add_task("[cyan]Processing...", total=count)

for i in range(count):
time.sleep(0.01) # Simulate work
progress.update(task, advance=1)

typer.echo("Done!")

if __name__ == "__main__":
app()

The progress bar updates in real-time, showing current progress, rate, and estimated time remaining. This is covered in depth in article 8.

Layout with Columns

Display content side-by-side:

import typer
from rich.console import Console
from rich.columns import Columns
from rich.panel import Panel

console = Console()
app = typer.Typer()

@app.command()
def compare_versions():
"""Compare two versions."""
old = Panel("[red]Version 1.0[/red]\n- Feature A\n- Feature B", title="Old")
new = Panel("[green]Version 2.0[/green]\n- Feature A\n- Feature B\n- Feature C", title="New")

console.print(Columns([old, new]))

if __name__ == "__main__":
app()

Columns automatically adjust to available terminal width, stacking vertically on narrow terminals.

Comparison Table: Output Approaches

MethodUse CaseEffortOutput Quality
print() / typer.echo()Simple textMinimalBasic
Rich markup textColored messagesLowGood
Rich tablesData displayLow-MediumExcellent
Rich panelsAlerts, emphasisLow-MediumExcellent
Rich syntaxCode, configsMediumProfessional
Rich progressLong operationsMediumProfessional

Key Takeaways

  • Rich library provides markup-based coloring with [color]text[/color] syntax.
  • Tables organize tabular data with automatic column sizing and alignment.
  • Panels create bordered sections perfect for alerts and important information.
  • Syntax highlighting makes code and config output professional and readable.
  • Progress bars show real-time operation status for long-running tasks.
  • Rich automatically degrades gracefully on terminals without color support.

Frequently Asked Questions

Does Rich work on Windows?

Yes. Rich detects Windows and enables VT100 escape sequences automatically on Windows 10+. For older Windows versions, output degrades to plain text.

Can I use Rich with Typer?

Yes. Import Rich's print() function in your Typer commands and use it directly. Typer and Rich are fully compatible.

How do I customize table colors and styling?

Use the style parameter on add_column() and add_row() with color names or hex codes. You can also set style on individual cells by using Rich's Cell objects.

What if my terminal is very narrow?

Rich automatically wraps text and adjusts layouts. Tables stack vertically, columns reflow, and panels shrink gracefully. Test with rich by resizing your terminal to verify responsiveness.

Can I export Rich output to a file?

Yes. Create a Console with file=open('output.txt', 'w') and all output goes to the file. Use force_terminal=True to force ANSI color codes in files.

Further Reading