Rich is a Python library for rich text and beautiful formatting in the terminal. It provides an extensive toolkit for building polished command-line interfaces: styled text with colors and formatting, tables with borders and alignment, progress bars, syntax highlighting, Markdown rendering, pretty-printed data structures, enhanced tracebacks, tree views, panels, layouts, live-updating displays, logging integration, and more. Rich works with any terminal that supports ANSI escape codes and automatically adapts its output to the capabilities of the environment -- from 4-bit color all the way to 24-bit truecolor, with automatic downgrade for limited terminals.
1. Quick Start
Get Rich installed and printing styled output in under a minute.
Install and verify
pip install rich
Verify the installation by running the built-in test card, which demonstrates most of Rich's features in a single output:
python -m rich
You should see a colorful display showcasing colors, styles, text justification, markup, tables, syntax highlighting, pretty printing, Markdown, and more.
Your first Rich print
from rich import print
print("Hello, [bold magenta]World[/bold magenta]!")
That single import replaces Python's built-in print() with Rich's version, which understands BBCode-like markup for inline styling. The output renders "World" in bold magenta.
A quick table
from rich.console import Console
from rich.table import Table
table = Table(title="Grocery List")
table.add_column("Item", style="cyan")
table.add_column("Qty", justify="right", style="green")
table.add_row("Apples", "6")
table.add_row("Bread", "1")
table.add_row("Milk", "2")
console = Console()
console.print(table)
Tip
Run
python -m richat any time to see a comprehensive demo of Rich's capabilities. It is the fastest way to confirm your terminal supports Rich output and to see what is available.
2. Installation
Requirements
| Requirement | Detail |
|---|---|
| Python | 3.8 or later |
| OS | Linux, macOS, Windows (Windows Terminal recommended) |
| Dependencies | pygments (syntax highlighting), markdown-it-py (Markdown parsing) -- installed automatically by pip |
Install from PyPI
pip install rich
Or with your preferred package manager:
# Poetry
poetry add rich
# Conda
conda install -c conda-forge rich
# pipx (for CLI utilities that use Rich)
pipx install rich
Development install
git clone https://github.com/Textualize/rich.git
cd rich
pip install -e ".[dev]"
Important
On Windows, use Windows Terminal for the best experience. The legacy
cmd.execonsole has limited color support. Rich auto-detects terminal capabilities and degrades gracefully, but colors and Unicode rendering will be reduced in older terminals.
Verify installation
>>> import rich
>>> rich.print("[green]Rich is working![/green]")
If you see green text, Rich is installed and your terminal supports styled output.
3. Configuration
Rich requires no configuration files. All behavior is controlled through Python constructor arguments and a small number of environment variables.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
UNICODE_VERSION |
latest |
Override the Unicode version used for character width calculation. Set to a specific version (e.g., 13.0.0) if your terminal renders certain characters at unexpected widths. |
DATABRICKS_RUNTIME_VERSION |
(unset) | Automatically detected. When present, Rich adjusts its behavior for Databricks notebook environments. |
NO_COLOR |
(unset) | Industry standard. When set to any value, Rich disables all color output. See no-color.org. |
TERM |
(varies) | Rich reads the standard TERM variable to determine terminal capabilities. Setting TERM=dumb forces plain-text output. |
COLUMNS |
(auto) | Override the detected terminal width. Useful for CI or piped output. |
FORCE_COLOR |
(unset) | When set, forces Rich to output color even when the output is not a TTY. |
Console configuration
The Console class is the central object in Rich. Most configuration happens at construction time:
from rich.console import Console
# Default: auto-detect everything
console = Console()
# Force a specific width (useful for testing or file output)
console = Console(width=120)
# Disable color
console = Console(no_color=True)
# Force terminal mode for non-TTY output (e.g., piping to a file)
console = Console(force_terminal=True)
# Write to stderr instead of stdout
console = Console(stderr=True)
# Use a custom file object
console = Console(file=open("output.txt", "w"))
# Enable recording for later export
console = Console(record=True)
Key Console constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
color_system |
str or None |
"auto" |
Force color system: "standard" (4-bit), "256" (8-bit), "truecolor" (24-bit), "windows", or None to disable |
force_terminal |
bool |
False |
Treat output as a terminal even if not a TTY |
force_jupyter |
bool |
False |
Force Jupyter notebook rendering |
width |
int |
(auto) | Override terminal width |
height |
int |
(auto) | Override terminal height |
no_color |
bool |
False |
Disable all color |
tab_size |
int |
8 |
Number of spaces per tab |
record |
bool |
False |
Enable output recording for export |
markup |
bool |
True |
Enable Rich markup parsing |
emoji |
bool |
True |
Enable emoji shortcode rendering |
highlight |
bool |
True |
Enable automatic highlighting |
log_time |
bool |
True |
Show timestamp in console.log() |
log_path |
bool |
True |
Show file path in console.log() |
Reconfiguring the global console
Rich maintains a global Console instance used by rich.print(). You can reconfigure it:
from rich import reconfigure
reconfigure(width=100, no_color=True)
Themes
Rich supports custom themes for consistent styling across your application:
from rich.console import Console
from rich.theme import Theme
custom_theme = Theme({
"info": "dim cyan",
"warning": "magenta",
"danger": "bold red",
})
console = Console(theme=custom_theme)
console.print("This is [info]information[/info]")
console.print("This is [warning]a warning[/warning]")
console.print("This is [danger]dangerous[/danger]")
Themes can also be loaded from INI-style config files:
# mytheme.ini
[styles]
info = dim cyan
warning = magenta
danger = bold red
from rich.theme import Theme
theme = Theme.read("mytheme.ini")
4. Core Usage
4.1 Printing Styled Text
The rich.print() drop-in replacement
from rich import print
print("Hello, [bold red]World[/bold red]!")
print("[underline]Underlined[/underline] and [strike]strikethrough[/strike]")
print("[on blue]White on blue background[/on blue]")
print(":thumbs_up: Emoji support with shortcodes")
rich.print() has the same signature as the built-in print() -- sep, end, file, and flush all work as expected.
Rich markup syntax
Rich uses a BBCode-like markup for inline styling. Tags are enclosed in square brackets:
| Markup | Effect |
|---|---|
[bold]text[/bold] |
Bold |
[italic]text[/italic] |
Italic |
[underline]text[/underline] |
Underlined |
[strike]text[/strike] |
Strikethrough |
[dim]text[/dim] |
Dimmed |
[reverse]text[/reverse] |
Reversed foreground/background |
[blink]text[/blink] |
Blinking (terminal support varies) |
[red]text[/red] |
Named color |
[#ff8800]text[/#ff8800] |
Hex color |
[on blue]text[/on blue] |
Background color |
[bold red on white]text[/] |
Combined styles, closed with [/] |
[link=URL]text[/link] |
Clickable hyperlink (terminal support varies) |
Tip
Use
[/]as a shorthand to close all open tags. You can also use short forms:[b]for bold,[i]for italic,[u]for underline.
The Console class
For full control, use Console directly:
from rich.console import Console
console = Console()
# Basic printing with styles
console.print("Hello", style="bold green")
# Logging with timestamp and file path
console.log("Server started", style="info")
# Print a rule (horizontal line)
console.rule("[bold blue]Section Header")
# Print with justification
console.print("Centered text", justify="center")
console.print("Right-aligned", justify="right")
Text objects for fine-grained control
from rich.text import Text
text = Text("Hello, World!")
text.stylize("bold", 0, 5) # "Hello" is bold
text.stylize("red", 7, 12) # "World" is red
text.highlight_regex(r"\w+", "cyan") # Highlight all words
console.print(text)
You can also construct Text from markup:
text = Text.from_markup("[bold]Hello[/], [italic red]World[/]!")
Style objects
from rich.style import Style
warning_style = Style(bold=True, color="yellow", bgcolor="red")
console.print("WARNING!", style=warning_style)
# Parse from string
style = Style.parse("bold italic cyan on dark_blue")
Note
Rich supports 16 standard color names (
red,green,blue, etc.), 256 extended colors viacolor(N)syntax, and 24-bit truecolor viargb(R,G,B)or hex#RRGGBB. Rich automatically converts colors to the best available representation for the current terminal.
4.2 Tables
Rich renders tables with borders, colors, column alignment, and automatic sizing.
Basic table
from rich.console import Console
from rich.table import Table
table = Table(title="Star Wars Movies")
table.add_column("Released", style="cyan", no_wrap=True)
table.add_column("Title", style="magenta")
table.add_column("Box Office", justify="right", style="green")
table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
table.add_row("Dec 15, 2017", "Star Wars Ep. VIII: The Last Jedi", "$1,332,539,889")
table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")
console = Console()
console.print(table)
Table configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
title |
str |
None |
Title displayed above the table |
caption |
str |
None |
Caption displayed below the table |
box |
Box |
HEAVY_HEAD |
Border style (see rich.box module) |
show_header |
bool |
True |
Show column headers |
show_footer |
bool |
False |
Show column footers |
show_edge |
bool |
True |
Show table border |
show_lines |
bool |
False |
Show lines between rows |
expand |
bool |
False |
Expand table to fill available width |
padding |
int/tuple |
(0, 1) |
Cell padding |
row_styles |
list |
None |
Alternating row styles, e.g. ["none", "dim"] |
Column options
table.add_column(
"Name",
justify="left", # "left", "center", "right", "full"
style="cyan", # Default cell style
header_style="bold", # Header cell style
no_wrap=True, # Prevent word wrapping
ratio=2, # Relative width ratio
min_width=10, # Minimum column width
max_width=30, # Maximum column width
overflow="ellipsis", # Overflow handling
)
Grid layout (borderless tables)
Table.grid() creates a borderless table useful for layouts:
table = Table.grid(padding=1, pad_edge=True)
table.add_column("Label", style="bold", justify="right")
table.add_column("Value")
table.add_row("Name:", "Rich")
table.add_row("Version:", "13.x")
table.add_row("Author:", "Will McGugan")
console.print(table)
Box styles
Rich includes many border styles in rich.box:
from rich import box
table = Table(box=box.ROUNDED) # Rounded corners
table = Table(box=box.SIMPLE) # Simple lines
table = Table(box=box.MINIMAL) # Minimal borders
table = Table(box=box.DOUBLE) # Double-line borders
table = Table(box=None) # No borders at all
4.3 Progress Bars
Simple progress with track()
The easiest way to add a progress bar:
from rich.progress import track
for item in track(range(100), description="Processing..."):
# do work
pass
The Progress class
For more control, use the Progress context manager:
from rich.progress import Progress
with Progress() as progress:
task = progress.add_task("[green]Downloading...", total=1000)
while not progress.finished:
progress.update(task, advance=1)
Multi-task progress
from rich.progress import Progress
with Progress() as progress:
task1 = progress.add_task("[red]Downloading...", total=1000)
task2 = progress.add_task("[green]Processing...", total=500)
task3 = progress.add_task("[cyan]Cooking...", total=200)
while not progress.finished:
progress.update(task1, advance=0.9)
progress.update(task2, advance=0.5)
progress.update(task3, advance=0.3)
Custom progress columns
from rich.progress import (
BarColumn,
DownloadColumn,
Progress,
TextColumn,
TimeRemainingColumn,
TransferSpeedColumn,
)
progress = Progress(
TextColumn("[bold blue]{task.fields[filename]}", justify="right"),
BarColumn(bar_width=None),
"[progress.percentage]{task.percentage:>3.1f}%",
DownloadColumn(),
TransferSpeedColumn(),
TimeRemainingColumn(),
)
Available built-in columns:
| Column | Description |
|---|---|
TextColumn |
Arbitrary text with task field interpolation |
BarColumn |
The progress bar itself |
SpinnerColumn |
An animated spinner |
TimeElapsedColumn |
Time since task started |
TimeRemainingColumn |
Estimated time remaining |
DownloadColumn |
Download progress in bytes/KB/MB/GB |
TransferSpeedColumn |
Transfer speed |
FileSizeColumn |
Total file size |
MofNCompleteColumn |
"M of N" style counter |
TaskProgressColumn |
Percentage complete |
File copy with progress
import shutil
from rich.progress import Progress
with Progress() as progress:
desc = "Copying data.bin"
with progress.open("data.bin", "rb", description=desc) as src:
with open("data_copy.bin", "wb") as dst:
shutil.copyfileobj(src, dst)
Note
progress.open()wraps a file object to automatically track read progress. There is alsoprogress.wrap_file()for wrapping arbitrary file-like objects (e.g., HTTP response streams).
Deferred task start
For downloads where you don't know the total size upfront:
task_id = progress.add_task("download", filename="data.zip", start=False)
# ... later, when you know the size:
progress.update(task_id, total=file_size)
progress.start_task(task_id)
4.4 Syntax Highlighting
Rich highlights source code using Pygments, with added features like line numbers and indent guides.
from rich.syntax import Syntax
code = '''
def hello(name: str) -> None:
"""Greet someone."""
print(f"Hello, {name}!")
'''
syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
console.print(syntax)
Load from file
syntax = Syntax.from_path("example.py", line_numbers=True, indent_guides=True)
console.print(syntax)
Syntax constructor options
| Parameter | Type | Default | Description |
|---|---|---|---|
code |
str |
(required) | Source code string |
lexer |
str |
(required) | Pygments lexer name ("python", "javascript", etc.) |
theme |
str |
"monokai" |
Pygments theme name |
line_numbers |
bool |
False |
Display line numbers |
start_line |
int |
1 |
Starting line number |
line_range |
tuple |
None |
Only show lines in (start, end) range |
highlight_lines |
set |
None |
Set of line numbers to highlight |
word_wrap |
bool |
False |
Wrap long lines |
indent_guides |
bool |
False |
Show indent guide lines |
padding |
int |
0 |
Padding around code |
background_color |
str |
None |
Override background color |
CLI utility
# Highlight a file in the terminal
python -m rich.syntax example.py --line-numbers --indent-guides --theme monokai
4.5 Markdown Rendering
Rich renders Markdown directly in the terminal:
from rich.markdown import Markdown
md = Markdown("""
# Heading 1
This is **bold** and *italic* text with `inline code`.
- Item one
- Item two
- Item three
```python
print("Hello from a code block!")
""")
console.print(md)
#### Markdown options
| Parameter | Type | Default | Description |
|---|---|---|---|
| `markup` | `str` | *(required)* | Markdown text |
| `code_theme` | `str` | `"monokai"` | Pygments theme for code blocks |
| `inline_code_lexer` | `str` | `None` | Lexer for inline code |
| `hyperlinks` | `bool` | `True` | Render clickable links (if terminal supports) |
| `justify` | `str` | `None` | Text justification |
#### CLI utility
```bash
# Render a markdown file in the terminal
python -m rich.markdown README.md --force-color --width 80
# With a specific code theme
python -m rich.markdown README.md --code-theme dracula
4.6 Pretty Printing
Rich's pretty printer displays Python data structures with indentation, syntax coloring, and smart wrapping.
Quick install for the REPL
from rich.pretty import install
install()
After calling install(), every expression result in the Python REPL is automatically pretty-printed. This is one of the most popular Rich features for interactive development.
Explicit pretty printing
from rich.pretty import pprint
data = {
"name": "Rich",
"features": ["colors", "tables", "progress bars"],
"nested": {"level1": {"level2": [1, 2, 3]}},
}
pprint(data, indent_guides=True, expand_all=True)
Pretty as a renderable
from rich.pretty import Pretty
from rich.panel import Panel
data = {"key": [1, 2, 3], "other": ("a", "b", "c")}
console.print(Panel(Pretty(data), title="Data"))
Auto-repr with @rich.repr.auto
Automatically generate a Rich-compatible __repr__ for your classes:
import rich.repr
@rich.repr.auto
class Bird:
def __init__(self, name, eats=None, fly=True, extinct=False):
self.name = name
self.eats = list(eats) if eats else []
self.fly = fly
self.extinct = extinct
from rich import print
print(Bird("penguin", eats=["fish"], fly=False))
Tip
Use
rich.pretty.install()in your Python startup file (PYTHONSTARTUPenvironment variable) to get Rich pretty printing in every interactive session automatically.
4.7 Tracebacks
Rich produces tracebacks with syntax highlighting, local variable display, and cleaner formatting than Python's default.
Global install (recommended for applications)
from rich.traceback import install
install(show_locals=True)
# Now ALL unhandled exceptions use Rich tracebacks automatically
Manual exception printing
from rich.console import Console
console = Console()
try:
1 / 0
except Exception:
console.print_exception(show_locals=True)
install() options
| Parameter | Type | Default | Description |
|---|---|---|---|
show_locals |
bool |
False |
Show local variables in each frame |
width |
int |
100 |
Width of traceback output |
extra_lines |
int |
3 |
Extra lines of code context around the error |
theme |
str |
None |
Pygments theme for code |
word_wrap |
bool |
False |
Wrap long lines |
suppress |
list |
[] |
List of modules/paths to suppress from tracebacks |
max_frames |
int |
100 |
Maximum frames to show (useful for recursion errors) |
Suppressing library frames
from rich.traceback import install
import click
install(suppress=[click])
# Click's internal frames will be dimmed in tracebacks
Warning
When using Rich tracebacks with
show_locals=True, be aware that local variable values are displayed in the traceback output. Avoid logging tracebacks that may contain sensitive data (passwords, tokens, API keys) to shared locations.
4.8 Trees
Display hierarchical data as an expandable tree:
from rich.tree import Tree
tree = Tree("Root")
branch1 = tree.add("Branch 1")
branch1.add("Leaf 1a")
branch1.add("Leaf 1b")
branch2 = tree.add("Branch 2")
branch2.add("[red]Leaf 2a[/red]")
sub = branch2.add("Sub-branch")
sub.add("Deep leaf")
console.print(tree)
Trees can contain any Rich renderable, including tables, panels, and syntax blocks:
from rich.tree import Tree
from rich.syntax import Syntax
tree = Tree("Project")
src = tree.add("src/")
src.add(Syntax("def main(): pass", "python"))
4.9 Panels and Layout
Panels
Wrap any content in a bordered panel:
from rich.panel import Panel
console.print(Panel("Hello, World!", title="Greeting", border_style="green"))
console.print(Panel.fit("Tight fit panel", border_style="red"))
Panel options include title, subtitle, border_style, padding, expand, and box (border style).
Alignment
from rich.align import Align
console.print(Align.center("Centered text"))
console.print(Align.right("Right-aligned"))
console.print(Align.center("Vertically centered", vertical="middle"))
Columns
Display items in auto-sized columns:
from rich.columns import Columns
from rich.panel import Panel
items = [Panel(f"Item {i}", expand=True) for i in range(12)]
console.print(Columns(items))
Layout (split-pane interfaces)
Build complex split-pane terminal UIs:
from rich.layout import Layout
layout = Layout()
layout.split(
Layout(name="header", size=3),
Layout(name="main", ratio=1),
Layout(name="footer", size=5),
)
layout["main"].split_row(
Layout(name="sidebar"),
Layout(name="body", ratio=2, minimum_size=60),
)
layout["header"].update("Header content")
layout["body"].update("Main body")
layout["sidebar"].update("Sidebar")
layout["footer"].update("Footer")
console.print(layout)
For a full-screen application with live updates, combine Layout with Live:
from rich.live import Live
with Live(layout, screen=True, refresh_per_second=10):
# Update layout regions dynamically
layout["body"].update("Updated content")
4.10 Live Display
Update terminal output in place without flickering:
from time import sleep
from rich.live import Live
from rich.table import Table
table = Table()
table.add_column("Row")
table.add_column("Value")
with Live(table, refresh_per_second=4):
for row in range(12):
sleep(0.4)
table.add_row(f"{row}", f"[red]value {row}")
Live options:
| Parameter | Type | Default | Description |
|---|---|---|---|
renderable |
any | (required) | The renderable to display |
console |
Console |
None |
Console instance |
screen |
bool |
False |
Use full-screen alternate buffer |
refresh_per_second |
float |
4 |
Maximum refresh rate |
transient |
bool |
False |
Clear display when done |
redirect_stdout |
bool |
True |
Redirect stdout through Live |
redirect_stderr |
bool |
True |
Redirect stderr through Live |
vertical_overflow |
str |
"ellipsis" |
How to handle overflow: "crop", "ellipsis", "visible" |
4.11 Logging with RichHandler
Replace Python's default logging output with Rich-formatted logs:
import logging
from rich.logging import RichHandler
logging.basicConfig(
level=logging.DEBUG,
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler()],
)
log = logging.getLogger("rich")
log.info("Server starting...")
log.warning("Low disk space")
log.error("Connection failed")
log.debug("Received %d bytes", 4096)
RichHandler options:
| Parameter | Type | Default | Description |
|---|---|---|---|
level |
int |
NOTSET |
Logging level |
console |
Console |
None |
Console to write to |
show_time |
bool |
True |
Show timestamp |
show_level |
bool |
True |
Show log level |
show_path |
bool |
True |
Show file path and line number |
markup |
bool |
False |
Enable Rich markup in log messages |
rich_tracebacks |
bool |
False |
Use Rich tracebacks for exceptions |
tracebacks_show_locals |
bool |
False |
Show locals in Rich tracebacks |
log_time_format |
str |
"[%X]" |
Time format string |
Tip
Set
markup=TrueonRichHandlerto use Rich markup in your log messages:log.info("[bold green]Success![/]"). Setrich_tracebacks=Trueto get Rich-formatted exception tracebacks in your logs.
Console.log()
Rich also provides a standalone log method that adds timestamps and source location:
from rich.console import Console
console = Console()
console.log("Starting process...")
console.log("Result:", {"status": "ok", "count": 42}, log_locals=True)
4.12 Prompts
Rich provides styled prompts for user input with built-in validation:
from rich.prompt import Prompt, IntPrompt, FloatPrompt, Confirm
# Basic text prompt
name = Prompt.ask("Enter your name")
# With a default value
name = Prompt.ask("Enter your name", default="World")
# Integer input with validation (re-prompts on invalid input)
age = IntPrompt.ask("Enter your age")
# Float input
weight = FloatPrompt.ask("Enter weight in kg")
# Yes/no confirmation
if Confirm.ask("Continue?"):
print("Proceeding...")
# Prompt with choices (validates input against choices)
color = Prompt.ask("Pick a color", choices=["red", "green", "blue"])
4.13 Status and Spinners
Status spinner
Show a spinner while a long-running task executes:
from rich.console import Console
console = Console()
with console.status("[bold green]Working on tasks...") as status:
# do work
import time
tasks = [f"task {n}" for n in range(1, 6)]
while tasks:
task = tasks.pop(0)
time.sleep(1)
console.log(f"{task} complete")
Spinner renderable
Use a spinner as a standalone renderable:
from rich.spinner import Spinner
spinner = Spinner("dots", text="Loading...")
# Use within a Live display or other container
Available spinner styles include: dots, line, pipe, simpleDots, simpleDotsScrolling, star, arc, bouncingBall, pong, and many more. See rich.spinner.SPINNERS for the full list.
4.14 JSON Pretty Printing
From the Python API
from rich import print_json
# From a JSON string
print_json('{"name": "Rich", "version": 13}')
# From a Python object
print_json(data={"users": [{"id": 1, "name": "Alice"}]})
From the Console
from rich.json import JSON
json_display = JSON('{"key": "value", "list": [1, 2, 3]}')
console.print(json_display)
CLI utility
# Pretty print a JSON file
python -m rich.json data.json
# From stdin
cat data.json | python -m rich.json -
# With custom indent
python -m rich.json data.json --indent 4
4.15 Inspect
Examine any Python object interactively:
from rich import inspect
# Basic inspection -- shows attributes and values
inspect([1, 2, 3])
# Show methods
inspect([1, 2, 3], methods=True)
# Show full help text
inspect([1, 2, 3], help=True)
# Show private attributes
inspect([1, 2, 3], private=True)
# Show everything
inspect([1, 2, 3], all=True)
inspect() is particularly useful in the REPL for exploring unfamiliar objects and APIs.
4.16 Exporting Output
Rich can capture and export rendered output to HTML, SVG, or plain text.
from rich.console import Console
from rich.table import Table
# Enable recording
console = Console(record=True)
table = Table(title="Data")
table.add_column("Name")
table.add_column("Value")
table.add_row("Alpha", "100")
table.add_row("Beta", "200")
console.print(table)
# Export methods (return strings)
html = console.export_html()
text = console.export_text()
svg = console.export_svg(title="My Output")
# Save methods (write to files)
console.save_html("output.html", clear=False)
console.save_text("output.txt", clear=False)
console.save_svg("output.svg", title="My Output")
Important
The console output buffer is flushed after each export call by default. Pass
clear=Falseto keep the buffer for subsequent exports. If you need multiple export formats from the same output, useclear=Falseon all but the last call.
4.17 Themes
Defining custom themes
from rich.console import Console
from rich.theme import Theme
theme = Theme({
"info": "dim cyan",
"warning": "magenta",
"danger": "bold red",
"success": "bold green",
"highlight": "italic yellow",
})
console = Console(theme=theme)
console.print("[success]Operation completed successfully[/success]")
console.print("[danger]Critical error occurred[/danger]")
Theme files
Save themes as INI files for reuse:
# styles.ini
[styles]
info = dim cyan
warning = magenta
danger = bold red
success = bold green
from rich.theme import Theme
theme = Theme.read("styles.ini")
4.18 Custom Renderables
Rich objects implement the "Rich protocol" -- any object can be made renderable by implementing __rich__() or __rich_console__():
Simple: __rich__()
Return any Rich renderable:
from rich.panel import Panel
from rich.text import Text
class Header:
def __init__(self, title):
self.title = title
def __rich__(self):
return Panel(Text(self.title, justify="center"), style="bold blue")
console.print(Header("My Application"))
Advanced: __rich_console__()
Yield Segment objects for complete control:
from rich.console import Console, ConsoleOptions, RenderResult
from rich.segment import Segment
from rich.style import Style
class ColorStripe:
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
for x in range(options.max_width):
color_index = int(x / options.max_width * 255)
yield Segment(" ", Style(bgcolor=f"color({color_index})"))
yield Segment.line()
console.print(ColorStripe())
4.19 Custom Highlighters
Create highlighters that automatically apply styles based on regex patterns:
from rich.console import Console
from rich.highlighter import RegexHighlighter
from rich.theme import Theme
class EmailHighlighter(RegexHighlighter):
base_style = "email."
highlights = [r"(?P<address>[\w.-]+@[\w.-]+\.\w+)"]
theme = Theme({"email.address": "bold magenta"})
console = Console(highlighter=EmailHighlighter(), theme=theme)
console.print("Send mail to [email removed] for more info")
The base_style is prepended to each named group in the regex. Define the resulting style names in your theme.
5. Common Tasks
Recipe 1: Styled error messages
from rich.console import Console
from rich.panel import Panel
console = Console(stderr=True)
def error(message: str) -> None:
console.print(Panel(f"[bold red]{message}", title="Error", border_style="red"))
error("File not found: config.yaml")
Recipe 2: Progress bar for file downloads
from concurrent.futures import ThreadPoolExecutor
from urllib.request import urlopen
from rich.progress import (
Progress, BarColumn, DownloadColumn, TransferSpeedColumn, TimeRemainingColumn
)
def download(url: str, path: str, progress, task_id):
response = urlopen(url)
size = int(response.info()["Content-length"])
progress.update(task_id, total=size)
progress.start_task(task_id)
with open(path, "wb") as f:
while chunk := response.read(32768):
f.write(chunk)
progress.update(task_id, advance=len(chunk))
progress = Progress(
"[bold blue]{task.fields[filename]}",
BarColumn(bar_width=None),
DownloadColumn(),
TransferSpeedColumn(),
TimeRemainingColumn(),
)
urls = ["https://example.com/file1.zip", "https://example.com/file2.zip"]
with progress:
with ThreadPoolExecutor(max_workers=4) as pool:
for url in urls:
filename = url.split("/")[-1]
task_id = progress.add_task("dl", filename=filename, start=False)
pool.submit(download, url, filename, progress, task_id)
Recipe 3: Directory listing with columns
import os
from rich import print
from rich.columns import Columns
from rich.text import Text
def list_directory(path: str):
entries = sorted(os.listdir(path), key=str.lower)
items = []
for name in entries:
full = os.path.join(path, name)
style = "bold blue" if os.path.isdir(full) else "default"
text = Text(name, style=style)
text.highlight_regex(r"\.\w+$", "bold")
items.append(text)
print(Columns(items, equal=True, column_first=True))
list_directory(".")
Recipe 4: Full-screen dashboard with Live + Layout
from datetime import datetime
from time import sleep
from rich.console import Console
from rich.layout import Layout
from rich.live import Live
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
layout = Layout()
layout.split(
Layout(name="header", size=3),
Layout(name="body", ratio=1),
)
def get_header():
return Panel(Text(datetime.now().ctime(), justify="center"), style="white on blue")
with Live(layout, screen=True, refresh_per_second=4) as live:
for i in range(60):
layout["header"].update(get_header())
layout["body"].update(Panel(f"Tick: {i}"))
sleep(1)
Recipe 5: Calendar with colored tables
import calendar
from rich import box
from rich.columns import Columns
from rich.console import Console
from rich.table import Table
def print_month(year: int, month: int):
cal = calendar.Calendar()
table = Table(
title=f"{calendar.month_name[month]} {year}",
box=box.SIMPLE_HEAVY, style="green", padding=0
)
for wd in cal.iterweekdays():
table.add_column(calendar.day_abbr[wd], justify="right")
for week in cal.monthdayscalendar(year, month):
table.add_row(*[str(d) if d else "" for d in week])
return table
console = Console()
months = [print_month(2026, m) for m in range(1, 13)]
console.print(Columns(months, padding=1, expand=True))
Recipe 6: Enhanced logging for a server
import logging
from rich.logging import RichHandler
from rich.highlighter import RegexHighlighter
from rich.theme import Theme
class RequestHighlighter(RegexHighlighter):
base_style = "req."
highlights = [
r"(?P<method>GET|POST|PUT|DELETE)",
r"(?P<status>[2]\d{2})",
r"(?P<error>[45]\d{2})",
r"(?P<path>/\S+)",
]
theme = Theme({
"req.method": "bold cyan",
"req.status": "bold green",
"req.error": "bold red",
"req.path": "magenta",
})
logging.basicConfig(
level=logging.INFO,
format="%(message)s",
handlers=[RichHandler(console=__import__("rich.console", fromlist=["Console"]).Console(theme=theme))],
)
Recipe 7: Exception handling with Rich tracebacks
from rich.traceback import install
install(show_locals=True, suppress=["click", "flask"])
# All unhandled exceptions now show Rich tracebacks
# with local variables visible and framework frames dimmed
Recipe 8: Nested progress bars
from time import sleep
from rich.console import Group
from rich.live import Live
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
overall = Progress()
jobs = Progress(SpinnerColumn(), TextColumn("{task.description}"), BarColumn())
group = Group(
Panel(Group(jobs), title="Current Jobs"),
overall,
)
overall_task = overall.add_task("Total", total=30)
with Live(group):
for i in range(3):
job_task = jobs.add_task(f"Job {i+1}", total=10)
for j in range(10):
sleep(0.1)
jobs.advance(job_task)
overall.advance(overall_task)
jobs.update(job_task, visible=False)
Recipe 9: Exporting a table as SVG
from rich.console import Console
from rich.table import Table
console = Console(record=True)
table = Table(title="Data Report")
table.add_column("Metric", style="cyan")
table.add_column("Value", justify="right", style="green")
table.add_row("Users", "1,234")
table.add_row("Revenue", "$56,789")
console.print(table, justify="center")
console.save_svg("report.svg", title="Data Report")
Recipe 10: Pretty-printed class instances
import rich.repr
@rich.repr.auto
class Config:
def __init__(self, host="localhost", port=8080, debug=False, workers=4):
self.host = host
self.port = port
self.debug = debug
self.workers = workers
from rich import print
print(Config(debug=True, workers=8))
# Output: Config(host='localhost', port=8080, debug=True, workers=8)
Recipe 11: Rendering Markdown files
from rich.console import Console
from rich.markdown import Markdown
console = Console()
with open("README.md") as f:
md = Markdown(f.read())
console.print(md)
Recipe 12: Interactive object exploration
from rich import inspect
# Discover what methods a string has
inspect("hello", methods=True)
# Explore a module
import json
inspect(json, methods=True, help=True)
6. Troubleshooting
Progress bars and Live display not working in Jupyter
Symptom: Progress bars or Live display produce garbled output or don't update in Jupyter notebooks, particularly with ipykernel>=7.
Cause: Changes in ipykernel 7+ changed how stdout/stderr are handled, breaking Rich's output detection.
Workaround:
from rich.console import Console
# Force Jupyter mode explicitly
console = Console(force_jupyter=True)
If that doesn't help, pin to an older ipykernel:
pip install "ipykernel<7"
Caution
This is a known open issue (#3868). If you encounter it, check the issue for the latest status and workarounds.
Colors not showing in output
Symptom: Output appears as plain text without any colors or styling.
Check the following:
-
Terminal support: Use a modern terminal (Windows Terminal, iTerm2, GNOME Terminal, etc.). Legacy
cmd.exehas limited support. -
Piped output: Rich disables color when output is piped to a file or another command. Force it with:
console = Console(force_terminal=True) -
NO_COLORenvironment variable: IfNO_COLORis set, Rich respects it and disables all color. Unset it if you want color. -
TERM=dumb: Rich treatsTERM=dumbas a no-color terminal. Check your environment.
Note
Run
python -m richto see a test card. If it renders with colors, Rich and your terminal are working correctly. If it renders without colors, the issue is with your terminal configuration.
Emoji and Unicode characters displaying incorrectly
Symptom: Emoji appear as question marks, boxes, or cause misaligned output.
Solutions:
-
Use a font with good Unicode coverage (e.g., Cascadia Code, JetBrains Mono, Nerd Fonts).
-
Set the
UNICODE_VERSIONenvironment variable to match your terminal's Unicode support:export UNICODE_VERSION=13.0.0 -
Disable emoji rendering:
console = Console(emoji=False)
Rich markup appearing as literal text
Symptom: [bold]text[/bold] prints literally instead of rendering as bold.
Causes:
-
Using Python's built-in
print()instead ofrich.print()orconsole.print(). -
Markup disabled on the Console:
Console(markup=False). -
Unescaped square brackets in text. Escape literal brackets:
console.print("Use \\[bold] for bold") # Escaped
VSCode Remote SSH traceback links
Symptom: When using Rich tracebacks over VSCode Remote SSH, file links in tracebacks point to local filesystem paths instead of the remote server.
Status: Known issue (#4036). The file paths in tracebacks reference the remote server's filesystem, but VSCode may not correctly resolve them as remote paths.
Windows legacy console issues
Symptom: Colors look wrong, or stderr output uses stdout's color handle.
Solutions:
- Use Windows Terminal instead of legacy
cmd.exeor PowerShell ISE. - Rich's legacy Windows color layer has a known issue (#4071) where it always uses the stdout handle, even for stderr streams. If you need correct stderr coloring, use Windows Terminal which supports native VT escape sequences.
Warning
The legacy Windows console adapter (
rich._win32_console) is a compatibility layer for older Windows terminals. It has known limitations. Always prefer Windows Terminal for the best Rich experience on Windows.
console.print() with no arguments
Symptom: console.print() with no arguments does not behave the same as print().
Explanation: Python's built-in print() outputs a newline when called with no arguments. console.print() also outputs a newline, but if you pass empty *args (e.g., console.print(*[])), the behavior differs. This is a known edge case (#3701).
7. FAQ
Q: Is Rich a CLI framework? A: No. Rich is a library for producing beautiful terminal output. It does not handle argument parsing, command routing, or CLI application structure. For a full CLI framework built on Rich, see Textual (TUI) or use Rich alongside Click or Typer.
Q: What are the python -m rich.* commands?
A: Rich includes small convenience utilities for quick terminal use:
python -m rich-- displays a feature demo test cardpython -m rich.json <file>-- pretty-prints a JSON filepython -m rich.syntax <file>-- syntax-highlights a source filepython -m rich.markdown <file>-- renders a Markdown file
These are convenience wrappers, not the primary interface. Rich is designed to be used as a Python library.
Q: How do I disable all Rich formatting? A: Several approaches:
Console(no_color=True)-- disables color but keeps layoutConsole(force_terminal=False)-- disables terminal features when not a TTY- Set
NO_COLOR=1environment variable - Set
TERM=dumbenvironment variable
Q: Does Rich work in Jupyter notebooks?
A: Yes. Rich auto-detects Jupyter and renders output as HTML in notebook cells. You can force this behavior with Console(force_jupyter=True). Note that ipykernel>=7 introduced a compatibility issue with progress bars (#3868).
Q: How do I use Rich with Python's logging module?
A: Use RichHandler:
import logging
from rich.logging import RichHandler
logging.basicConfig(level=logging.INFO, handlers=[RichHandler()])
See Section 4.11 for full details.
Q: Can I export Rich output to HTML or images?
A: Yes. Create a Console with record=True, print your content, then use export_html(), export_svg(), or export_text(). See Section 4.16.
Q: How do I make my own objects printable with Rich?
A: Implement __rich__() (return a renderable) or __rich_console__() (yield Segments) on your class. See Section 4.18.
Q: What color systems does Rich support?
A: Rich supports 4-bit (16 standard colors), 8-bit (256 colors), and 24-bit truecolor (16.7 million colors). It auto-detects the terminal's capability and converts colors down automatically. You can force a specific system with Console(color_system="truecolor").
Q: How do I use Rich alongside other libraries that write to the terminal?
A: If another library writes directly to stdout while Rich is using Live or Progress, output can become garbled. Use Live(redirect_stdout=True) (the default) to capture stdout writes, or use console.log() for your own output within a Live context.
Q: What is the performance impact of Rich?
A: Rich adds negligible overhead for simple print operations. For complex renderables (large tables, progress bars), Rich caches style calculations internally. The test card (python -m rich) benchmarks cold-cache and warm-cache render times so you can evaluate performance on your system.
For architecture details, rendering internals, and advanced customization, see the Admin Guide.