pyguides

Beautiful Terminal Output with Rich

Rich is a Python library that lets you build beautiful terminal output with colors, styles, tables, progress bars, syntax highlighting, and markdown rendering. Unlike plain print statements that produce unstyled monochrome text, Rich gives you fine-grained control over every aspect of your terminal interface. It works on Linux, macOS, and Windows, automatically detecting terminal capabilities and degrading gracefully when a feature is not supported.

Why Rich?

Standard print statements are plain and utilitarian. Rich makes your CLI applications look professional with minimal effort:

from rich.console import Console

console = Console()
console.print("Hello, [bold red]World![/bold red]")

That one line produces bold red text in any terminal that supports colors.

The core concept behind Rich is the markup language embedded in strings passed to console.print(). Square brackets enclose style directives that apply to the text between them, and closing tags restore the previous style. This inline approach means you can style individual words or phrases without breaking your output into multiple print calls. The Console object manages all the terminal detection and escape code generation, so you focus on what you want to display rather than how the terminal interprets control sequences.

Installation

Install Rich with pip:

pip install rich

That’s it. No configuration needed.

Rich is a pure Python package with no compiled extensions, so installation is fast and works across all platforms. Once installed, you import the Console class and start printing styled text. There are no configuration files, environment variables, or terminal setup steps to worry about. The library auto-detects whether your terminal supports colors and advanced features, falling back to plain text output when necessary.

Getting Started

The Console object is your main entry point:

from rich.console import Console

console = Console()

# Basic colored text
console.print("[bold blue]This is bold blue[/bold blue]")
console.print("[italic green]This is italic green[/italic green]")
console.print("[underline yellow]This is underlined yellow[/underline yellow]")

# Combine styles
console.print("[bold cyan]Bold cyan[/bold cyan] with [inverse]inverse[/inverse]")

Rich uses a simple markup syntax similar to BBCode. Wrap text in square brackets with style names.

The style tags shown above, like [bold blue], [italic green], and [underline yellow], represent just a fraction of what Rich supports. Each tag can combine multiple attributes: you can write [bold italic underline red] to apply three text styles and a color simultaneously. The closing tag [/] or [/bold italic underline red] restores the previous style, and tags can nest to create layered effects. This declarative approach keeps your print statements readable even as the styling grows complex.

Available Colors

Rich supports 8-bit, true color (24-bit), and terminal default colors:

# Standard colors
console.print("[red]Red[/red]")
console.print("[blue]Blue[/blue]")
console.print("[green]Green[/green]")

# Bright variants
console.print("[bright_red]Bright Red[/bright_red]")
console.print("[bright_cyan]Bright Cyan[/bright_cyan]")

# 256-color (0-255)
console.print("[color(202)]Copper orange[/color(202)]")

# True color (RGB)
console.print("[rgb(255,128,0)]Orange[/rgb(255,128,0)]")

Rich’s color system goes beyond the 16 standard ANSI colors that most terminal libraries offer. The 256-color palette (accessed with color(N)) covers a wide range of hues suitable for data visualization and status indicators, while the true-color RGB syntax gives you access to over 16 million colors for precise branding and theming. When your terminal does not support a requested color depth, Rich automatically finds the closest available match instead of producing garbled output.

Text Styles

Beyond colors, Rich provides numerous text styles:

console.print("[bold]Bold text[/bold]")
console.print("[dim]Dim text[/dim]")
console.print("[italic]Italic text[/italic]")
console.print("[underline]Underlined text[/underline]")
console.print("[strike]Strikethrough[/strike]")
console.print("[blink]Blinking text[/blink]")
console.print("[reverse]Reversed colors[/reverse]")
console.print("[conceal]Hidden text[/conceal]")

Not all terminals support all styles—Rich gracefully degrades when they don’t.

Colors and text styles handle individual lines, but real CLI applications often need structured layouts. Rich provides several layout primitives including tables, panels, trees, and columns that render complex data as formatted ASCII art. Each one uses the same Console object and markup language, so you can mix styled text inside any layout component without switching to a different API.

Tables

Rich can render beautiful ASCII tables:

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

console = Console()

table = Table(title="Programming Languages")

table.add_column("Language", style="cyan", no_wrap=True)
table.add_column("Creator", style="magenta")
table.add_column("Year", justify="right", style="green")

table.add_row("Python", "Guido van Rossum", "1991")
table.add_row("JavaScript", "Brendan Eich", "1995")
table.add_row("Rust", "Graydon Hoare", "2010")
table.add_row("Go", "Robert Griesemer", "2009")

console.print(table)

The output looks like a proper terminal table with borders and aligned columns.

The Table class handles column sizing, text wrapping, and alignment automatically. You specify the data and Rich figures out the layout. Each column can have its own style, justification, and width constraints. The no_wrap=True parameter on the Language column prevents long entries from breaking across lines, while justify="right" on the Year column aligns numbers in a way that makes comparison easy. These formatting choices add up to output that looks professionally designed rather than hastily assembled.

Table Styles

Customize table appearance:

table = Table(show_header=True, header_style="bold magenta")
table.add_column("Name", style="cyan")
table.add_column("Status", justify="center")

# Different row styles
table.add_row("Task 1", "[green]✓ Complete[/green]")
table.add_row("Task 2", "[yellow]⟳ In Progress[/yellow]")
table.add_row("Task 3", "[red]✗ Failed[/red]")

console.print(table)

You can also use Table.row_styles for alternating row colors.

Tables present static snapshots of data, but many CLI tools need to show ongoing activity like downloads, file processing, or batch operations that take more than a fraction of a second. Rich’s progress bar system fills this gap with animated bars, spinners, and percentage counters that give users real-time feedback without cluttering the terminal with repeated output lines.

Progress Bars

Rich includes built-in progress tracking:

from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn

with Progress(
    TextColumn("[progress.description]{task.description}"),
    BarColumn(),
    TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
) as progress:
    
    task = progress.add_task("[cyan]Processing...", total=100)
    
    for i in range(100):
        progress.update(task, advance=1)

The progress context manager handles the rendering loop internally. You call update(task, advance=N) and Rich redraws the bar on the next refresh cycle. The columns defined in the Progress constructor determine what information appears: description text, a visual bar, and a percentage counter in this case. You can customize the layout by choosing from built-in column types like SpinnerColumn, BarColumn, TextColumn, TimeElapsedColumn, and TimeRemainingColumn.

For multiple concurrent tasks:

from rich.progress import Progress

with Progress() as progress:
    task1 = progress.add_task("[red]Downloading...", total=100)
    task2 = progress.add_task("[blue]Installing...", total=100)
    
    for i in range(100):
        progress.update(task1, advance=1)
        
    for i in range(100):
        progress.update(task2, advance=0.5)
        progress.update(task2, advance=0.5)

When you create multiple tasks inside a single Progress context, Rich renders them as stacked bars that update independently. This is useful for pipelines where one stage feeds into another, like a download step followed by an extraction step. Each task tracks its own completion percentage, and Rich handles the terminal redraw so the bars stay aligned even as they advance at different rates.

Track Function

For simple progress tracking, use track:

from rich.progress import track

for i in track(range(100), description="Processing..."):
    do_work()

The track function is a convenience wrapper around the full Progress API. It accepts any iterable and displays a progress bar that advances automatically as you consume items. This is ideal for the common case where progress maps directly to iteration, like processing a list of files, iterating over database rows, or looping through a numeric range. For more complex scenarios where progress does not advance linearly, use the Progress context manager with explicit update calls instead.

Logging

Rich can replace or augment Python’s logging:

from rich.logging import RichHandler
import logging

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

logger = logging.getLogger(__name__)
logger.info("This is an info message")
logger.warning("This is a warning")
logger.error("This is an error")

The RichHandler adds color-coded log levels and optionally includes tracebacks.

Python’s standard logging module produces plain text output that is functional but visually dense. The RichHandler replaces the default formatter with styled output: error messages appear in red, warnings in yellow, and info messages in the default color. Setting rich_tracebacks=True goes a step further. When an exception occurs, Rich renders a syntax-highlighted traceback with local variable values, making debugging significantly faster than reading through unformatted stack frames.

Syntax Highlighting

Rich includes a syntax highlighter using the same themes as VS Code:

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

code = """
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

for i in range(10):
    print(f"F({i}) = {fibonacci(i)}")
"""

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

Available themes include monokai, github-dark, dracula, and many more.

Rich’s syntax highlighter uses Pygments under the hood, so every language and theme that Pygments supports is available. The line_numbers=True parameter adds gutter numbers that match what you would see in an editor, and the theme parameter accepts any Pygments style name. This is particularly useful for CLI tools that display code snippets: linters, REPLs, and documentation browsers can all use syntax highlighting to make code examples easier to read.

Markdown

Render Markdown directly in the terminal:

from rich.markdown import Markdown
from rich.console import Console

console = Console()

markdown = """
# Heading 1

This is **bold** and *italic* text.

> This is a quote

- Item 1
- Item 2
"""

console.print(Markdown(markdown))

The Markdown renderer converts headings, bold and italic text, code blocks, blockquotes, and lists into their Rich-styled equivalents. This means you can write documentation or help text in Markdown, a format many developers already know, and display it directly in the terminal without converting to plain text first. The renderer supports inline code spans, fenced code blocks with language tags, and nested formatting within list items.

Panels

Group content in boxes:

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

console = Console()

console.print(Panel("[bold cyan]Welcome![/bold cyan]", expand=False))
console.print(Panel("This is a simple panel"))
console.print(Panel.fit("[green]Fit to content[/green]"))

console.print(Panel("ASCII", border_style="ascii"))
console.print(Panel("Double", border_style="double"))
console.print(Panel("Rounded", border_style="rounded"))

Panels are simple containers that draw a border around their content. The expand parameter controls whether the panel stretches to fill the terminal width or fits tightly around the text, and border_style accepts any Rich style string including colors and text attributes. Panels work well for status messages, configuration summaries, and welcome screens where you want to visually separate a block of information from surrounding output.

Trees

Display hierarchical data:

from rich.tree import Tree
from rich.console import Console

console = Console()

tree = Tree("📁 project/")
tree.add("📄 README.md")
tree.add("📁 src/")
tree.add("📁 tests/")
subtree = tree.add("📁 lib/")
subtree.add("📄 utils.py")

console.print(tree)

Trees display hierarchical relationships using indented lines and branch connectors. Each node can contain styled text, emoji, or even nested Rich renderables, and the .add() method returns the child node so you can chain further additions. This makes trees a natural fit for directory listings, class hierarchies, menu structures, and any data with a parent-child relationship.

Status Indicators

Show temporary status messages:

from rich.console import Console

console = Console()
with console.status("[bold cyan]Loading data...") as status:
    do_time_consuming_work()
    status.update("[bold green]Done!")

This displays a spinner while your code runs.

The status context manager shows a temporary message with an animated spinner, which is ideal for operations that take a few seconds and don’t need a percentage counter. Unlike progress bars, the status indicator occupies a single line and disappears when the context exits, keeping the terminal clean. You can update the message mid-operation by calling status.update(), which is useful for showing which phase of a multi-step process is currently running.

Live Updates

Use Live for real-time terminal updates:

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

console = Console()

with Live("Initializing...", console=console) as live:
    for i in range(10):
        live.update(f"Step {i+1}/10")
        # Do work here

The Live context manager gives you full control over what appears on screen during a long-running operation. Unlike the simpler progress bar and status APIs, Live accepts any Rich renderable (tables, panels, trees, or custom layouts) and redraws them in place on each call to live.update(). This is the tool to reach for when you need a dynamic dashboard that updates multiple values simultaneously, such as a system monitor showing CPU, memory, and network stats in real time.

Combining Everything

Here’s a more complete example combining several features:

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

console = Console()

# Welcome panel
console.print(Panel.fit(
    "[bold cyan]Welcome to MyApp v1.0[/bold cyan]",
    border_style="cyan"
))

# Status table
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Component", style="cyan")
table.add_column("Status", justify="center")
table.add_row("Database", "[green]● Connected[/green]")
table.add_row("Cache", "[green]● Active[/green]")
table.add_row("API", "[yellow]● Rate limited[/yellow]")

console.print(table)
console.print()
console.print("[bold]Server running at[/bold] [link=http://localhost:8000]http://localhost:8000[/link]")

See Also