Rich Cheatsheet

A visual guide to Rich covering styled printing and markup, tables, trees, panels and layout, progress bars and spinners, syntax highlighting and pretty tracebacks, live displays, and the Console plus logging handler.

python
rich
cheatsheet
Author

James Balamuta

Published

July 20, 2026

Rich is the library for beautiful, expressive output in the terminal. Instead of fighting raw ANSI escape codes, you describe what you want with a BBCode-style markup ("[bold red]error[/]") and hand structured objects (a Table, a Tree, a Panel, a Progress bar) to a single sink that measures, colors, and draws them as crisp boxed frames. The recurring mental model in this sheet is one picture: a Console object on the left is the single output sink, and every renderable (a markup string, a grid, a tree, a box, a bar, highlighted code, a live view) flows along a gray arrow into that console and lands as a rounded terminal frame on the right. Where this looks like a plotting or app-building tool, the contrast is the point: Rich is not a plotting library (it draws bar-like progress and tables in text, not figures), it is not a full TUI framework (that is Textual, Rich’s sibling, for interactive apps), and it is not a logging framework (it is a handler you plug into the stdlib logging module). The conventional imports are from rich import print for a markup-aware drop-in and from rich.console import Console; console = Console() for everything else, and everything here is Rich v15.

Complete Rich cheatsheet (light mode): eight panels covering styled printing and markup, tables, trees and columns, panels and layout, progress bars and spinners, syntax highlighting and pretty tracebacks, live updating displays, and the Console plus logging handler.

Complete Rich cheatsheet (dark mode): eight panels covering styled printing and markup, tables, trees and columns, panels and layout, progress bars and spinners, syntax highlighting and pretty tracebacks, live updating displays, and the Console plus logging handler.

Download the full cheatsheet

All eight panels in a single, printable SVG.

Light SVG Dark SVG

Tables

A Table is the workhorse: construct it, declare columns with add_column (each can set justification, color, and wrapping), feed rows with add_row, then hand the whole object to console.print, which measures the content and draws an aligned, bordered grid for you. Table.grid() is the same engine with the borders off, which makes it the simplest way to lay out aligned label and value pairs or compose other renderables.

Rich table panel: make a titled table, add styled columns, add a data row, render to the console, a borderless grid helper, per-cell color and footers.

Add columns, add rows, print; Rich measures and draws the grid.

Rich table panel: make a titled table, add styled columns, add a data row, render to the console, a borderless grid helper, per-cell color and footers.

Add columns, add rows, print; Rich measures and draws the grid.
from rich.table import Table
table = Table(title="Movies")                       # a titled grid

table.add_column("Released", justify="right", style="cyan", no_wrap=True)
table.add_column("Title", style="magenta")          # styled columns
table.add_row("Dec 20, 2019", "The Rise of Skywalker")  # a data row
console.print(table)                                # measure + draw the grid

grid = Table.grid(padding=1); grid.add_row("name:", "ada")  # borderless layout
table.add_row("2021", "[red]flop[/red]")            # per-cell color

See Tables. Table.grid() is the same engine with the borders turned off.

Trees and Columns

A Tree models any hierarchy: start from a root label, and every .add(...) returns the new node so you can nest deeper by adding onto the returned branch, which is ideal for file listings, dependency graphs, or nested config. To place several renderables next to each other instead of stacked, wrap them in Columns, which flows them into balanced newspaper-style columns sized to the terminal width.

Rich tree panel: start a tree from a root, add a branch that is returned, nest deeper, render the tree, lay renderables side by side with Columns, style guides and labels.

Nest .add(...) for hierarchies; flow renderables into columns.

Rich tree panel: start a tree from a root, add a branch that is returned, nest deeper, render the tree, lay renderables side by side with Columns, style guides and labels.

Nest .add(...) for hierarchies; flow renderables into columns.
from rich.tree import Tree
tree = Tree("project")              # a root label

branch = tree.add("src")            # add() returns the new branch
branch.add("app.py")                # nest deeper on the returned node
branch.add("utils.py")
console.print(tree)                 # draw the indented guide lines

from rich.columns import Columns
console.print(Columns(panels, equal=True))   # side-by-side renderables

Tree("[bold]root[/bold]", guide_style="dim") # style the guide lines

See Tree and Columns. Each .add(...) returns the node so you can keep nesting.

Panels and Layout

A Panel draws a titled, rounded box around any renderable (text, a table, a tree), and Panel.fit shrinks the box to the content instead of the full width; console.rule(...) draws a labeled horizontal divider. For a split screen, a Layout carves the terminal into named regions you grow with split_column and split_row and then address by name (layout["body"]), which is the skeleton for a Live dashboard.

Rich panel panel: box a renderable, shrink the box to fit, center and align content, add a horizontal rule, split the screen into regions, address a named region.

Box anything in a Panel; split the screen with Layout.

Rich panel panel: box a renderable, shrink the box to fit, center and align content, add a horizontal rule, split the screen into regions, address a named region.

Box anything in a Panel; split the screen with Layout.
from rich.panel import Panel
console.print(Panel("Hello", title="greeting"))    # box a renderable

console.print(Panel.fit("snug", subtitle="v15"))   # shrink the box to fit

from rich.align import Align
console.print(Align.center("centered"))            # center content
console.rule("[bold]Part 2[/bold]")                # a titled divider

from rich.layout import Layout
layout = Layout()
layout.split_column(Layout(name="top"), Layout(name="body"))  # split the screen
layout["body"].split_row(Layout(name="left"), Layout(name="right"))  # by name

See Panel and Layout. Address a named region with layout["body"].

Progress Bars and Spinners

The fastest win is track(iterable, description=...), which wraps any loop in a live progress bar; for more control, a Progress object lets you compose columns (spinner, bar, counts, time remaining) and drive one or more tasks by hand with add_task and update(..., advance=...). When the work has no measurable total, console.status(...) shows a transient spinner that disappears when the block exits.

Rich progress panel: progress over any iterable with track, a custom multi-column bar, drive tasks manually, multiple concurrent tasks, a transient spinner with status, wrap a file read with a bar.

Wrap any loop with track; build custom bars with Progress.

Rich progress panel: progress over any iterable with track, a custom multi-column bar, drive tasks manually, multiple concurrent tasks, a transient spinner with status, wrap a file read with a bar.

Wrap any loop with track; build custom bars with Progress.
from rich.progress import track
for item in track(items, description="Working..."):   # one-line loop bar
    ...

from rich.progress import Progress, SpinnerColumn, BarColumn, MofNCompleteColumn
with Progress() as p:                                 # custom multi-column bar
    t = p.add_task("download", total=100)             # drive a task by hand
    p.update(t, advance=10)                           # nudge the fill forward
    p.add_task("encode", total=50)                    # multiple concurrent tasks

with console.status("Loading...", spinner="dots"):    # transient spinner
    ...

from rich.progress import wrap_file                   # stream a file read

See Progress display. track is the one-liner; Progress gives you full column control.

Syntax and Pretty Tracebacks

Syntax runs your code through Pygments and renders it with a theme, line numbers, and highlighting, either from a string or with Syntax.from_path(...) straight from a file, and pretty.pprint and print_json give the same treatment to Python objects and JSON. The headline feature is traceback.install(), which replaces Python’s plain tracebacks with syntax-highlighted frames that can show local variables, making errors far easier to read.

Rich syntax panel: highlight a code string, highlight a whole file, pretty-print any object, highlight JSON, install pretty tracebacks, render a caught exception.

Pygments-highlighted code, JSON, and gorgeous error frames.

Rich syntax panel: highlight a code string, highlight a whole file, pretty-print any object, highlight JSON, install pretty tracebacks, render a caught exception.

Pygments-highlighted code, JSON, and gorgeous error frames.
from rich.syntax import Syntax
console.print(Syntax(code, "python", theme="monokai", line_numbers=True))  # highlight
console.print(Syntax.from_path("app.py", line_numbers=True))   # read from disk

from rich.pretty import pprint
pprint({"a": [1, 2, 3], "b": {"x": 1}})            # color-coded object tree

from rich import print_json
print_json('{"id": 1, "ok": true}')               # highlighted JSON

from rich.traceback import install
install(show_locals=True)                          # pretty tracebacks everywhere
console.print_exception(show_locals=True)          # render the caught exception

See Syntax and Tracebacks. install(show_locals=True) shows locals on every error.

Live Updating Displays

Live claims a region of the terminal and refreshes one renderable in place rather than scrolling, so you call live.update(new_renderable) (or mutate a table the Live is already showing) to redraw a dashboard, a growing table, or a status panel without spamming the screen. Tune refresh_per_second to throttle redraws and set transient=True when you want the region to vanish once the block ends.

Rich live panel: live-update one renderable, swap the displayed renderable, mutate then auto-refresh, live progress and log together, throttle the redraw rate, build the frame each tick.

Refresh one region in place; great for dashboards and status.

Rich live panel: live-update one renderable, swap the displayed renderable, mutate then auto-refresh, live progress and log together, throttle the redraw rate, build the frame each tick.

Refresh one region in place; great for dashboards and status.
from rich.live import Live
with Live(table, refresh_per_second=4) as live:    # update one region in place
    live.update(new_table)                         # swap the displayed renderable
    table.add_row("new")                           # or mutate; it auto-refreshes

from rich.console import Group
Live(Group(panel, progress))                       # a panel over a progress bar

Live(renderable, refresh_per_second=10, transient=True)  # throttle + vanish on exit
live.update(make_table())                          # build a fresh frame each tick

See Live display. transient=True makes the region vanish once the block ends.

Console and Logging Handler

The Console object is the single output sink behind everything: it handles width detection, themes (name your own styles with Theme), console.log(...) with timestamps and caller info, and record=True so you can later export_text or export_svg. To bring Rich into an existing program, add a RichHandler to the standard library’s logging config and your ordinary log.info, log.warning, and log.error calls come out colored, aligned, and (optionally) with rich tracebacks.

Rich console panel: make a configured console, print with a timestamp and caller, name your own styles with Theme, wire into stdlib logging, leveled colored log output, record output to export.

One Console object, plus a drop-in handler for stdlib logging.

Rich console panel: make a configured console, print with a timestamp and caller, name your own styles with Theme, wire into stdlib logging, leveled colored log output, record output to export.

One Console object, plus a drop-in handler for stdlib logging.
from rich.console import Console
console = Console()                                # the single output sink

console.log("saved", {"rows": 42})                 # timestamp + caller file:line

from rich.theme import Theme
Console(theme=Theme({"ok": "bold green", "err": "bold red"}))  # named styles

import logging
from rich.logging import RichHandler
logging.basicConfig(level="INFO", format="%(message)s", handlers=[RichHandler()])
log = logging.getLogger("app")
log.info("up"); log.warning("slow"); log.error("down")  # leveled colored output

Console(record=True).export_svg("out.svg")         # capture and replay output

See Console API and Logging handler. Add a RichHandler to color stdlib logging.

Quick Reference

Key Rich calls.
Command What it does Area
from rich import print Pretty, markup-aware drop-in print Print
print("[bold red]hi[/]") Inline style markup Print
Console().print(x, style=...) Print a renderable with a style Print
Table(); add_column; add_row Build and draw an aligned grid Tables
Table.grid() Borderless layout grid Tables
Tree("root"); branch = t.add(...) Nested hierarchy, branch returned Trees
Columns(items) Side-by-side renderables Trees
Panel("x", title=...) Box a renderable with a title Panels
Layout().split_column(...) Carve the screen into regions Layout
track(iterable, description=...) One-line loop progress bar Progress
with Progress() as p: p.add_task(...) Custom multi-task bars Progress
console.status("...", spinner=...) Transient spinner Progress
Syntax(code, "python", ...) Highlight code Syntax
traceback.install(show_locals=True) Pretty exceptions everywhere Tracebacks
with Live(renderable) as live: ... Refresh a region in place Live
Console() / console.log(...) The output sink + timestamped log Console
RichHandler() in logging.basicConfig Colored stdlib logging Logging
What the Console exposes.
Member Does
console.print(*renderables, style=...) Render anything to the terminal
console.log(*objects) Print with timestamp and caller file:line
console.rule(title) Draw a labeled horizontal divider
console.status(msg, spinner=...) Context-managed transient spinner
console.print_exception(show_locals=...) Render the current exception, rich style
console.print_json(json_str) Pretty, highlighted JSON
Console(record=True) + export_text() / export_svg() Capture and replay output
Console(theme=Theme({...})) Register named styles
console.width, console.size Detected terminal dimensions
Rich renderables at a glance.
Import Renderable Use for
from rich.table import Table Table Tabular data, aligned grids
from rich.tree import Tree Tree Hierarchies, file trees
from rich.panel import Panel Panel Boxed, titled content
from rich.columns import Columns Columns Side-by-side layout
from rich.layout import Layout Layout Split-screen regions
from rich.progress import Progress Progress Progress bars and tasks
from rich.syntax import Syntax Syntax Highlighted source code
from rich.markdown import Markdown Markdown Rendered Markdown
from rich.text import Text Text Programmatic styled spans
from rich.live import Live Live In-place updating displays
Rich console markup.
Markup Effect
[bold]...[/bold] or [b]...[/b] Bold
[italic]...[/italic] or [i]...[/i] Italic
[underline]...[/underline] Underline
[red], [green], [#8b5cf6] Foreground color (name or hex)
[on yellow] Background color
[bold red on white] Combine in one tag
[link=https://...]text[/link] Clickable hyperlink
[/] Close the most recent tag
:rocket: :warning: Emoji shortcodes
\[literal] or escape("[x]") Print literal brackets

Appendix: Sample Code

The Console mental model

from rich.console import Console

console = Console()

console.print("[bold magenta]Hello[/] :wave:")        # markup + emoji
console.print("alert", style="bold white on red")     # a style override
console.rule("[bold]Section[/bold]")                  # a titled divider
console.log("processed", {"rows": 42})                # timestamp + caller file:line

A table, a tree, and a panel

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

console = Console()

table = Table(title="Movies")
table.add_column("Released", justify="right", style="cyan", no_wrap=True)
table.add_column("Title", style="magenta")
table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker")
console.print(table)

tree = Tree("project")
src = tree.add("src")
src.add("app.py")
src.add("utils.py")
console.print(tree)

console.print(Panel.fit("All systems go", title="status", subtitle="v15"))

Progress over a loop, and a custom multi-column bar

import time
from rich.progress import (
    track, Progress, SpinnerColumn, BarColumn,
    TextColumn, MofNCompleteColumn, TimeRemainingColumn,
)

# The one-liner: wrap any iterable.
for _ in track(range(20), description="Working..."):
    time.sleep(0.05)

# A custom bar with explicit columns and a manual task.
with Progress(
    SpinnerColumn(),
    TextColumn("[progress.description]{task.description}"),
    BarColumn(),
    MofNCompleteColumn(),
    TimeRemainingColumn(),
) as progress:
    task = progress.add_task("download", total=100)
    while not progress.finished:
        progress.update(task, advance=5)
        time.sleep(0.02)

Syntax highlighting and pretty tracebacks

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

console = Console()

code = '''def greet(name: str) -> None:
    print(f"hi {name}")
'''
console.print(Syntax(code, "python", theme="monokai", line_numbers=True))

# Replace every uncaught traceback with a rich, locals-aware one.
install(show_locals=True)

try:
    1 / 0
except ZeroDivisionError:
    console.print_exception(show_locals=True)

A live dashboard that refreshes in place

import time
from rich.live import Live
from rich.table import Table

def make_table(tick: int) -> Table:
    t = Table(title=f"tick {tick}")
    t.add_column("metric")
    t.add_column("value", justify="right")
    t.add_row("requests", str(tick * 7))
    t.add_row("errors", "[red]0[/red]")
    return t

with Live(make_table(0), refresh_per_second=4) as live:
    for tick in range(1, 11):
        time.sleep(0.25)
        live.update(make_table(tick))   # redraws the same region, no scroll

Rich as your stdlib logging handler

import logging
from rich.logging import RichHandler

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

log = logging.getLogger("app")
log.info("started")              # green INFO row, timestamp, caller
log.warning("disk almost full")  # amber WARNING row
log.error("upload failed")       # red ERROR row

Behavior notes

  • There is no rich.__version__. Rich does not expose the attribute; get the version with importlib.metadata.version("rich") or pip show rich. This trips people up.
  • The pretty drop-in shadows the builtin only where you import it. from rich import print is intentional; use from rich import print as rprint if you want both in the same module.
  • Close tags with the shorthand [/]. It closes the most recent style, so you rarely need to spell out [/bold magenta]. Both are valid on Rich 15.
  • Console.print controls markup and highlight. Pass markup=False to print a string literally and highlight=False to turn off automatic number and URL highlighting.
  • Prefer the column-list Progress constructor. The legacy auto_refresh keyword still works, but composing explicit columns (SpinnerColumn(), BarColumn(), …) is the current idiom.

References

Rich documentation (stable)

Project and related