# Rich — Administrator & Developer Guide

Rich is a Python library for rendering rich text, tables, progress bars, syntax-highlighted code, tracebacks, and dozens of other formatted elements in the terminal. It is the foundational rendering engine behind Textual and is used by thousands of CLI tools across the Python ecosystem. This guide covers its internal architecture, configuration surface, performance characteristics, security considerations, and extension points at the depth required for library maintainers, framework integrators, and contributors.

---

## 1. Architecture Overview

### 1.1 Design Philosophy

Rich follows a **renderable protocol** architecture. Every visual element — tables, panels, trees, progress bars, markdown — is a Python object that implements a simple protocol method. The `Console` class orchestrates rendering by calling that protocol on each object, collecting a stream of `Segment` tuples, and writing them to the output file descriptor. This design means Rich has no plugin registry, no configuration files, and no runtime services. All behavior is determined by constructor parameters and method calls at the point of use.

### 1.2 Rendering Pipeline

The full rendering path from `console.print(obj)` to terminal output:

```
console.print(*objects)
  │
  ├─ For each object:
  │    ├─ If str and markup=True → render_markup() → Text with Spans
  │    ├─ If has __rich__() → call it → get renderable
  │    ├─ If has __rich_console__() → use directly
  │    └─ Else → Pretty(obj) fallback
  │
  ├─ render() called on each renderable
  │    ├─ Builds ConsoleOptions (max_width, encoding, etc.)
  │    ├─ Calls renderable.__rich_console__(console, options)
  │    │    └─ Yields Segment(text, style, control) tuples
  │    └─ Applies style inheritance via StyleStack
  │
  ├─ Segment stream post-processing:
  │    ├─ Padding / alignment via Align, Padding renderables
  │    ├─ Overflow handling (fold, crop, ellipsis)
  │    ├─ Cell width normalization (Unicode-aware)
  │    └─ Color downsampling (truecolor → 256 → 16 → none)
  │
  └─ Output:
       ├─ file.write() for each line buffer
       ├─ If record=True → also append to internal buffer
       └─ If is_jupyter → HTML repr via JupyterMixin
```

> [!NOTE]
> The rendering pipeline is **entirely synchronous and single-threaded** per Console instance. The only threading in Rich occurs in `Live` (which uses `_RefreshThread` for periodic screen updates) and `Progress` (which inherits from `Live`). Console itself is not thread-safe — concurrent `print()` calls from multiple threads will interleave output. Use a `threading.Lock` or dedicate one Console per thread.

### 1.3 Component Inventory

| Component | Path | Role |
|---|---|---|
| Console | `rich/console.py` | Central orchestrator: rendering, recording, export, paging, alternate screen |
| Text | `rich/text.py` | Styled text with span-based formatting, wrapping, highlighting |
| Style | `rich/style.py` | Immutable style descriptors (color, bold, italic, etc.), combinable via `+` |
| Segment | `rich/segment.py` | Atomic output unit: `(text, style, control)` named tuple |
| Theme | `rich/theme.py` | Named style collections, stackable via `ThemeStack` |
| Color | `rich/color.py` | Color parsing, representation, and downsampling across color systems |
| Markup | `rich/markup.py` | BBCode-like `[bold red]text[/]` parser → Text with spans |
| Table | `rich/table.py` | Tabular layout with auto-sizing columns, borders, padding |
| Progress | `rich/progress.py` | Multi-task progress display with 14 built-in column types |
| Live | `rich/live.py` | Live-updating display region (alternate screen or inline) |
| Layout | `rich/layout.py` | Spatial partitioning via `Splitter` (row/column) tree |
| Syntax | `rich/syntax.py` | Pygments-backed code highlighting with line numbers and themes |
| Markdown | `rich/markdown.py` | CommonMark rendering via `markdown-it-py` with 15+ element types |
| Traceback | `rich/traceback.py` | Enhanced exception display with source context and locals |
| Pretty | `rich/pretty.py` | Structural pretty-printing with depth limits and highlighting |
| Panel | `rich/panel.py` | Box-bordered container for any renderable |
| Tree | `rich/tree.py` | Recursive tree display with guide lines |
| Columns | `rich/columns.py` | Auto-flowing column layout for iterables |
| Prompt | `rich/prompt.py` | Interactive input with validation (`Prompt`, `Confirm`, `IntPrompt`, `FloatPrompt`) |
| Spinner | `rich/spinner.py` | Animated spinner using data from `_spinners.py` |
| Status | `rich/status.py` | Spinner + message display (wraps `Live`) |
| JSON | `rich/json.py` | Syntax-highlighted JSON rendering |
| RichHandler | `rich/logging.py` | `logging.Handler` subclass for stdlib integration |
| Highlighter | `rich/highlighter.py` | ABC for text pattern highlighting (Regex, Repr, JSON, ISO8601) |
| Bar | `rich/bar.py` | Horizontal bar renderable |
| Rule | `rich/rule.py` | Horizontal rule with optional title |
| Align | `rich/align.py` | Horizontal/vertical alignment wrapper |
| Padding | `rich/padding.py` | Whitespace padding wrapper |
| Constrain | `rich/constrain.py` | Width-constraining wrapper |
| Screen | `rich/screen.py` | Full-screen renderable |
| Emoji | `rich/emoji.py` | Emoji name resolution and rendering |
| Control | `rich/control.py` | Terminal control sequences (cursor, clear, etc.) |
| Pager | `rich/pager.py` | Output paging via system pager |
| Box | `rich/box.py` | 30+ predefined border styles for panels and tables |
| FileProxy | `rich/file_proxy.py` | Wraps file objects to pass through Console |
| AnsiDecoder | `rich/ansi.py` | Converts raw ANSI escape sequences back to Rich Text |
| Palette | `rich/palette.py` | Color palette matching (nearest color in palette) |
| Measurement | `rich/measure.py` | Width measurement protocol |
| Protocol | `rich/protocol.py` | `is_renderable()`, `rich_cast()` helpers |
| Diagnose | `rich/diagnose.py` | `report()` prints terminal capability diagnostic |
| Export Format | `rich/_export_format.py` | HTML and SVG Jinja-style templates for recorded output |

### 1.4 Internal / Private Modules

| Module | Purpose |
|---|---|
| `_emoji_codes.py` | Emoji name → Unicode codepoint mapping dictionary |
| `_emoji_replace.py` | Regex-based emoji shortcode substitution in text |
| `_extension.py` | IPython `%load_ext rich` extension loader |
| `_fileno.py` | Safe `get_fileno()` that handles missing `.fileno()` |
| `_inspect.py` | `Inspect` renderable: introspect any Python object |
| `_log_render.py` | `LogRender` helper used internally by `RichHandler` |
| `_loop.py` | `loop_first()`, `loop_last()`, `loop_first_last()` iteration helpers |
| `_null_file.py` | `NullFile` — no-op IO object for quiet mode |
| `_palettes.py` | Standard 16-color and 256-color palette data |
| `_pick.py` | `pick_bool()` — first non-None boolean selector |
| `_ratio.py` | `ratio_resolve()`, `ratio_reduce()`, `ratio_distribute()` — integer layout math |
| `_spinners.py` | Spinner animation frame data (ported from cli-spinners) |
| `_stack.py` | `Stack(list)` — minimal push/pop stack |
| `_timer.py` | Context manager for timing code blocks |
| `_win32_console.py` | `LegacyWindowsTerm` — direct Win32 API console rendering |
| `_windows.py` | `WindowsConsoleFeatures` — detect VT support, truecolor |
| `_windows_renderer.py` | `legacy_windows_render()` — segment-to-Win32-API translation |
| `_wrap.py` | Word-wrapping algorithm with CJK and Unicode awareness |
| `_unicode_data/` | 23 Unicode version cell-width tables for `cell_len()` |

### 1.5 Dependency Graph (Internal)

```
Console ──→ Text ──→ Style ──→ Color ──→ Palette
  │           │                    │
  │           ├──→ Segment         ├──→ ColorSystem (enum)
  │           └──→ Highlighter     └──→ _palettes
  │
  ├──→ Theme ──→ ThemeStack
  ├──→ Markup ──→ Text
  ├──→ Measure ──→ Measurement
  ├──→ Control
  ├──→ Pager
  ├──→ _win32_console (Windows legacy path)
  └──→ _export_format (record/export)

Table ──→ Console, Text, Style, Segment, Box, Measure, Padding
Progress ──→ Live ──→ Console, _RefreshThread
Syntax ──→ Pygments, Text, Style, Segment
Markdown ──→ markdown-it-py, Text, Style, Syntax (for code blocks)
Traceback ──→ Syntax, Text, Panel, Columns, Highlighter
Pretty ──→ Text, Highlighter, _loop
RichHandler ──→ Console, Text, Highlighter, _log_render
```

---

## 2. System Requirements

### 2.1 Python Version Support

Rich requires **Python 3.8+** (as declared in `pyproject.toml`). The codebase uses conditional imports from `typing_extensions` for features not available until Python 3.11.

### 2.2 Runtime Dependencies

| Package | Version | Purpose |
|---|---|---|
| `markdown-it-py` | `>=2.2.0` | CommonMark parsing for `rich.markdown` |
| `pygments` | `>=2.13.0,<3.0.0` | Syntax highlighting for `rich.syntax` and code blocks |
| `typing_extensions` | `>=4.0.0,<5.0` | Backport of `TypeAlias`, `Protocol`, etc. (Python < 3.11 only) |

These are the only hard dependencies. Rich has **no C extensions**, no compiled components, and no system library requirements (with one caveat on Windows — see below).

### 2.3 Optional / Soft Dependencies

| Package | Used By | Effect If Missing |
|---|---|---|
| `ipywidgets` | `rich.jupyter` | Jupyter display degrades to plain text |
| `IPython` | `rich._extension`, `rich.pretty` | `%load_ext rich` unavailable; `pretty.install()` skips IPython hook |

### 2.4 Platform-Specific Behavior

**Linux / macOS:** Full feature set. Color system auto-detected from `$TERM` and `$COLORTERM` environment variables. Emoji rendering depends on terminal font support.

**Windows:** Rich includes a complete Windows compatibility layer:

- `_windows.py` detects console VT processing support via `GetConsoleMode` Win32 API
- `_win32_console.py` provides `LegacyWindowsTerm` — a full Win32 console API renderer that maps Rich styles to `SetConsoleTextAttribute` calls when VT sequences are not available
- `_windows_renderer.py` translates `Segment` streams into legacy Windows console calls
- Windows Terminal, PowerShell 7+, and modern `cmd.exe` (Windows 10 1607+) support VT sequences natively; legacy console falls back to 16-color Win32 API rendering

**Jupyter:** Auto-detected via `ipywidgets` availability. Output rendered as HTML via `JupyterMixin._repr_mimebundle_()`. Some features (Live, Progress animation, alternate screen) are unavailable or limited.

**Databricks:** Detected via `DATABRICKS_RUNTIME_VERSION` environment variable. Affects terminal feature detection in `Console.__init__()`.

> [!IMPORTANT]
> When embedding Rich in environments with non-standard file descriptors (e.g., subprocess pipes, socket-backed streams, or serverless runtimes), use `Console(force_terminal=True)` to bypass `isatty()` detection, and set `width=N` explicitly since `os.get_terminal_size()` will fail or return incorrect values.

---

## 3. Configuration Reference

Rich is configured entirely through constructor parameters and method calls — there are no configuration files. This section documents every configuration surface.

### 3.1 Environment Variables

Rich reads exactly two environment variables:

| Variable | Default | Effect |
|---|---|---|
| `UNICODE_VERSION` | `"latest"` (currently Unicode 15.1) | Selects the Unicode version table used by `cell_len()` for calculating character display widths. Affects all width calculations including wrapping, alignment, and table column sizing. Set to e.g. `"13.0.0"` for terminals with older Unicode support. Tables are in `rich/_unicode_data/`. |
| `DATABRICKS_RUNTIME_VERSION` | Not set | When present, Rich treats the environment as a Databricks notebook, adjusting terminal detection logic in `Console`. |

Additionally, Rich respects the **`NO_COLOR`** convention (see [no-color.org](https://no-color.org)): when the `NO_COLOR` environment variable is set (to any value), `Console` auto-detects `no_color=True`, disabling all color output while preserving structural formatting.

> [!IMPORTANT]
> `NO_COLOR` is not read as an environment variable directly in all code paths. Instead, Console checks `no_color` parameter first, then falls back to `os.environ.get("NO_COLOR")`. If you pass `no_color=False` explicitly, it overrides the environment variable.

### 3.2 Console Constructor — Complete Parameter Reference

The `Console` class constructor accepts the following parameters. These control all rendering behavior for that Console instance.

```python
Console(
    *,
    color_system: Optional[str] = "auto",  # "auto"|"standard"|"256"|"truecolor"|"windows"|None
    force_terminal: Optional[bool] = None,  # Override isatty() detection
    force_jupyter: Optional[bool] = None,   # Override Jupyter detection
    force_interactive: Optional[bool] = None,  # Override interactive detection
    soft_wrap: bool = False,        # If True, don't wrap text (let terminal handle it)
    theme: Optional[Theme] = None,  # Custom theme for named styles
    stderr: bool = False,           # Write to stderr instead of stdout
    file: Optional[IO[str]] = None, # Explicit output file (overrides stderr)
    quiet: bool = False,            # Suppress all output
    width: Optional[int] = None,    # Override detected terminal width
    height: Optional[int] = None,   # Override detected terminal height
    style: Optional[StyleType] = None,  # Default style for all output
    no_color: Optional[bool] = None,    # Disable color (respects NO_COLOR env)
    tab_size: int = 8,              # Tab stop width
    record: bool = False,           # Enable recording for export
    markup: bool = True,            # Enable [markup] parsing in strings
    emoji: bool = True,             # Enable :emoji: parsing
    emoji_variant: Optional[str] = None,  # "text"|"emoji" variant selector
    highlight: bool = True,         # Enable auto-highlighting
    log_time: bool = True,          # Show timestamp in console.log()
    log_path: bool = True,          # Show caller path in console.log()
    log_time_format: Union[str, FormatTimeCallable] = "[%X]",  # Time format
    highlighter: HighlighterType = ReprHighlighter(),  # Default highlighter
    legacy_windows: Optional[bool] = None,  # Force legacy Windows rendering
    safe_box: bool = True,          # Use ASCII box chars on legacy Windows
    get_datetime: Optional[Callable[[], datetime]] = None,  # Custom clock
    get_time: Optional[Callable[[], float]] = None,  # Custom monotonic clock
)
```

### 3.3 Color System Resolution

When `color_system="auto"` (the default), Console resolves the color system through this chain:

```
1. force_jupyter=True → no terminal colors (HTML output)
2. file is not a TTY and force_terminal not set → None (no color)
3. Windows legacy console → ColorSystem.WINDOWS (16 colors via Win32 API)
4. $COLORTERM == "truecolor" or "24bit" → ColorSystem.TRUECOLOR
5. "256color" in $TERM → ColorSystem.EIGHT_BIT (256 colors)
6. Default → ColorSystem.STANDARD (16 colors via ANSI)
```

The four `ColorSystem` enum values control how `Color` objects are downsampled:

| ColorSystem | Value | Colors | Method |
|---|---|---|---|
| `STANDARD` | 1 | 16 | Map to nearest ANSI color |
| `EIGHT_BIT` | 2 | 256 | Map to nearest xterm-256 palette entry |
| `TRUECOLOR` | 3 | 16.7M | Direct RGB `\x1b[38;2;R;G;Bm` |
| `WINDOWS` | 4 | 16 | Win32 `SetConsoleTextAttribute` (legacy path) |

### 3.4 Theme System

Themes are dictionaries mapping style names to `Style` objects. They are used to customize the appearance of built-in renderables.

```python
from rich.theme import Theme
from rich.console import Console

custom_theme = Theme({
    "info": "dim cyan",
    "warning": "magenta",
    "danger": "bold red",
    "repr.number": "bold green",     # Override built-in repr highlighting
    "progress.percentage": "cyan",   # Override progress bar colors
    "table.header": "bold magenta",  # Override table header style
})

console = Console(theme=custom_theme)
```

Built-in named styles (defined in `rich/default_styles.py`) include styles for every renderable: `progress.*`, `bar.*`, `table.*`, `tree.*`, `markdown.*`, `syntax.*`, `traceback.*`, `logging.*`, `inspect.*`, `json.*`, `prompt.*`, and more. The complete list is in `DEFAULT_STYLES` in that module.

Themes stack: `console.push_theme(theme)` / `console.pop_theme()` or use the `ThemeContext` context manager via `console.use_theme(theme)`.

### 3.5 Traceback Configuration

`rich.traceback.install()` replaces `sys.excepthook` globally:

```python
from rich.traceback import install

install(
    width=100,              # Output width
    extra_lines=3,          # Source context lines around error
    theme="monokai",        # Pygments theme for source code
    word_wrap=True,          # Wrap long lines
    show_locals=False,       # Show local variables at each frame
    locals_max_length=10,    # Truncate local collections
    locals_max_string=80,    # Truncate local strings
    locals_hide_dunder=True, # Hide __dunder__ locals
    locals_hide_sunder=False,# Hide _sunder locals
    indent_guides=True,      # Show indent guides in source
    suppress=[],             # List of modules to suppress frames from
    max_frames=100,          # Maximum frames to show
)
```

### 3.6 Logging Configuration

`RichHandler` integrates with Python's `logging` module:

```python
import logging
from rich.logging import RichHandler

logging.basicConfig(
    level="NOTSET",
    format="%(message)s",
    datefmt="[%X]",
    handlers=[RichHandler(
        rich_tracebacks=True,        # Use Rich for exception tracebacks
        tracebacks_show_locals=False,
        markup=True,                  # Parse [markup] in log messages
        show_time=True,
        show_level=True,
        show_path=True,
        enable_link_path=True,        # Make file paths clickable (OSC 8)
        highlighter=ReprHighlighter(),
        log_time_format="[%X]",
    )]
)
```

### 3.7 Pretty Printer Configuration

`rich.pretty.install()` replaces the default REPL display hook:

```python
from rich.pretty import install

install(
    console=None,            # Use default Console if None
    overflow="fold",         # "fold"|"crop"|"ellipsis"
    crop=False,              # Crop output to terminal width
    indent_guides=True,      # Show indent guides
    max_length=None,         # Max items to show in collections
    max_string=None,         # Max string length
    max_depth=None,          # Max nesting depth
    expand_all=False,        # Always expand collections
)
```

---

## 4. Deployment & Scaling

Rich is a library, not a service. "Deployment" means embedding it into applications. This section covers integration patterns and scaling considerations.

### 4.1 Installation

```bash
pip install rich
```

For minimal installs (e.g., Docker images where size matters), Rich's total install footprint including dependencies is approximately 5 MB.

### 4.2 Integration Patterns

**Pattern 1: Global Console Singleton**

Rich provides `rich.get_console()` which returns a module-level default `Console`. The public API functions `rich.print()`, `rich.print_json()`, and `rich.inspect()` all use this singleton. Reconfigure it with:

```python
import rich
rich.reconfigure(width=120, no_color=True)  # Modifies the global console
```

This pattern suits simple CLI tools. For libraries, create your own `Console` to avoid polluting the global state.

**Pattern 2: Dedicated Console per Component**

```python
from rich.console import Console

error_console = Console(stderr=True, style="bold red")
log_console = Console(file=open("output.log", "w"), width=120, no_color=True)
user_console = Console(highlight=True, markup=True)
```

**Pattern 3: Recording and Export**

```python
console = Console(record=True)
console.print(table)
console.print(syntax)
html_output = console.export_html(inline_styles=True)  # For email/web embedding
svg_output = console.export_svg(title="My Output")     # For documentation
text_output = console.export_text()                      # Plain text
```

> [!NOTE]
> Recording buffers all output in memory. For long-running processes, call `console.clear()` periodically to prevent unbounded memory growth, or use separate `Console` instances for recorded vs. live output.

**Pattern 4: File Output (CI/CD, Logging)**

```python
with open("report.txt", "w") as f:
    console = Console(file=f, width=120, force_terminal=True)
    console.print(table)  # ANSI codes written to file
    
# Or without color:
with open("report.txt", "w") as f:
    console = Console(file=f, width=120, no_color=True)
    console.print(table)  # Plain text, still formatted
```

### 4.3 Embedding in Libraries

Libraries that depend on Rich should:

1. **Create private Console instances** — never import or modify `rich.get_console()`
2. **Accept a `console` parameter** — let callers inject their own Console for testing or output redirection
3. **Respect `NO_COLOR`** — Rich does this automatically, but document it for your users
4. **Pin to compatible versions** — Rich follows semver; pin `rich>=13.0,<15`

### 4.4 Jupyter Integration

Rich auto-detects Jupyter environments and renders as HTML. Force or disable with:

```python
console = Console(force_jupyter=True)   # Force HTML rendering
console = Console(force_jupyter=False)  # Force terminal rendering in notebook
```

> [!WARNING]
> Known issue (#3868): Progress bars and Live displays do not work correctly with `ipykernel>=7` due to changes in IPython's display mechanism. Workaround: pin `ipykernel<7` or use static output methods instead of animated displays.

---

## 5. Operations

### 5.1 Diagnostic Tools

**Terminal capability report:**

```bash
python -m rich.diagnose
```

This calls `rich.diagnose.report()` and prints: Python version, Rich version, detected console dimensions, color system, encoding, and whether the terminal is interactive, a TTY, Jupyter, or in legacy Windows mode. Run this first when debugging rendering issues.

**Feature showcase:**

```bash
python -m rich
```

Renders a demonstration card showing tables, trees, syntax highlighting, markdown, progress bars, and other renderables. Useful for verifying the terminal supports Rich's output correctly.

### 5.2 CLI Sub-Modules

Rich ships four CLI-usable modules:

```bash
# JSON pretty-printer
python -m rich.json data.json
python -m rich.json data.json --indent 4

# Syntax highlighter
python -m rich.syntax script.py
python -m rich.syntax script.py --theme monokai --line-numbers --wrap --guides

# Markdown renderer
python -m rich.markdown README.md
python -m rich.markdown README.md --code-theme dracula --hyperlinks

# Feature demo
python -m rich
```

### 5.3 Common Operational Issues

> [!CAUTION]
> **Thread safety:** `Console` is **not thread-safe**. Concurrent `print()` calls from multiple threads produce interleaved, garbled output. Either use a lock, or create one `Console` per thread. The `Live` and `Progress` classes use internal locks for their refresh thread, but the underlying Console write is still unprotected if you also call `console.print()` from another thread while Live/Progress is active.

> [!WARNING]
> **Memory with `record=True`:** Every call to `console.print()` appends segments to an internal list when recording is enabled. In long-running applications (servers, daemons), this will leak memory indefinitely. Either disable recording, or call `console.clear()` after each `export_*()` call.

**Troubleshooting Checklist:**

| Symptom | Cause | Fix |
|---|---|---|
| No colors in output | `isatty()` returns False (pipe, redirect, CI) | `Console(force_terminal=True)` |
| Wrong colors / garbled output | Terminal doesn't support detected color system | `Console(color_system="standard")` or `Console(color_system="256")` |
| Broken box characters | Legacy Windows console or wrong encoding | `Console(safe_box=True)` (default) or use `box.ASCII` |
| Wide characters misaligned | Terminal's Unicode version differs from Rich's | Set `UNICODE_VERSION` env var to match terminal |
| Emoji not rendering | Terminal font lacks emoji glyphs | Use `emoji_variant="text"` or `Console(emoji=False)` |
| Traceback links don't work | Terminal doesn't support OSC 8 hyperlinks | Normal — links degrade to plain text |
| VSCode Remote SSH tracebacks link to local paths | Known issue (#4036) | File paths in tracebacks point to the remote filesystem; VSCode maps them to local |
| `Live` flickers or corrupts | Printing to console outside Live context | All output must go through the Live's console while active |
| Progress stalls in Jupyter | `ipykernel>=7` compatibility (#3868) | Pin `ipykernel<7` or use non-animated output |
| Elapsed time stuck after restart | Known issue (#3705) | `stop_task()` then `start_task()` does not reset elapsed tracking correctly |

### 5.4 Testing Rich-Dependent Code

**Capture output for assertions:**

```python
from rich.console import Console

console = Console(file=StringIO(), force_terminal=True, width=80, color_system="truecolor")
console.print("[bold]Hello[/bold]")
output = console.file.getvalue()
assert "\x1b[1mHello\x1b[0m" in output
```

**Strip styles for content-only testing:**

```python
console = Console(file=StringIO(), no_color=True, width=80)
console.print(my_table)
plain_output = console.file.getvalue()
assert "Expected Value" in plain_output
```

**Use `Console.capture()`:**

```python
console = Console(width=80)
with console.capture() as capture:
    console.print("[bold]test[/bold]")
output = capture.get()
```

---

## 6. Performance

### 6.1 Rendering Cost Model

Rich's rendering is CPU-bound, dominated by:

1. **Cell width calculation** (`cell_len()` in `cells.py`) — called for every string segment to calculate display width. Uses a binary search over Unicode version tables in `_unicode_data/`. This is the single hottest path.
2. **Style combination** — `Style.__add__()` merges style attributes. Called once per span boundary during text rendering.
3. **Color downsampling** — converting truecolor to 256-color or 16-color palettes requires Euclidean distance calculations across the palette. Only triggered when the color system is less than truecolor.
4. **Text wrapping** — `_wrap.py` performs word-boundary analysis with full Unicode awareness. Cost scales with text length and number of wrap points.

### 6.2 Performance Characteristics by Renderable

| Renderable | Relative Cost | Bottleneck |
|---|---|---|
| `Text` | Low | `cell_len()` for width calculation |
| `Panel` | Low | Border rendering + inner content |
| `Table` (small) | Low-Medium | Column width measurement (measures all cells) |
| `Table` (1000+ rows) | High | Full row measurement pass, segment generation |
| `Syntax` (small file) | Medium | Pygments tokenization + style mapping |
| `Syntax` (10K+ lines) | High | Pygments tokenization dominates |
| `Markdown` | Medium | `markdown-it-py` parsing + recursive rendering |
| `Tree` (deep) | Medium | Recursive rendering, guide character computation |
| `Progress` | Low per-tick | Main cost is the refresh rate, not rendering |
| `Pretty` (deep objects) | High | `traverse()` walks entire object graph |

### 6.3 Optimization Techniques

**Reduce unnecessary measurement:**

```python
# Pre-set column widths to skip auto-measurement
table = Table()
table.add_column("Name", width=20)     # Fixed width, no measurement
table.add_column("Value", ratio=1)     # Ratio-based, minimal measurement
```

**Control Pretty traversal depth:**

```python
from rich.pretty import Pretty

# Limit depth for large/nested objects
console.print(Pretty(huge_dict, max_depth=3, max_length=50))
```

**Minimize Live/Progress refresh rate:**

```python
# Default is 12.5 fps — reduce for lower CPU usage
with Progress(refresh_per_second=4) as progress:
    ...
```

**Avoid record mode in production:**

```python
# Don't use record=True unless you need export
console = Console(record=False)  # Default, but be explicit in perf-critical paths
```

### 6.4 Startup Cost

Rich's import cost is moderate (~50-100ms on typical hardware) due to:
- Loading `_emoji_codes.py` (large dictionary)
- Loading `_spinners.py` (animation data)
- Importing `pygments` (Rich imports it at module level in `syntax.py`)
- Loading Unicode cell-width tables

For CLI tools where startup latency matters, use **lazy imports**:

```python
def render_table(data):
    from rich.table import Table  # Import only when needed
    from rich.console import Console
    ...
```

---

## 7. Security

### 7.1 Input Handling

Rich processes untrusted input in several contexts. The security surface is small because Rich is a rendering library with no network, filesystem, or subprocess operations (with one exception: `SystemPager`).

> [!WARNING]
> **Markup injection:** If you pass user-controlled strings to `console.print()` with `markup=True` (the default), malicious input containing `[link=https://evil.com]click here[/link]` will render as a clickable hyperlink in terminals that support OSC 8. This could be used for phishing. Mitigations:
> - Use `console.print(user_input, markup=False)` for untrusted input
> - Use `rich.markup.escape(user_input)` to neutralize markup tags before embedding in markup strings
> - Use `Text(user_input)` which never parses markup

**ANSI injection via `AnsiDecoder`:** The `AnsiDecoder` class in `rich/ansi.py` parses raw ANSI escape sequences into Rich `Text`. If the input contains malicious escape sequences (e.g., terminal control codes that rewrite the prompt), `AnsiDecoder` will convert them into Rich styles but will NOT pass through raw control sequences. However, be aware that `AnsiDecoder` is a best-effort parser — unusual or malformed sequences may produce unexpected output.

### 7.2 SystemPager Security

`Console.pager()` can invoke the system pager (typically `less` or `more`):

```python
with console.pager():
    console.print(long_text)
```

This calls `subprocess.Popen(pager_command, shell=True)` using the `$PAGER` environment variable. If `$PAGER` is set to a malicious command, it will execute. This is standard Unix behavior and matches how `git`, `man`, and other tools work. In untrusted environments, use `console.pager(pager=Pager())` with a custom `Pager` class that does not invoke subprocesses.

### 7.3 Export Security

- `export_html()` does **not** sanitize output for XSS. If Rich renders user-controlled text and you embed the HTML in a web page, standard XSS vectors apply. Sanitize the output before serving.
- `export_svg()` embeds text in SVG `<text>` elements. Same XSS caveat applies if served in a web context.

> [!CAUTION]
> Never serve `export_html()` or `export_svg()` output directly in a web application without sanitization when the rendered content includes user-controlled input. Rich's export is designed for local viewing and documentation, not for untrusted web contexts.

### 7.4 Dependency Security

Rich has three runtime dependencies. The security surface of each:

- **`pygments`** — parses source code for tokenization. It does not execute code. No known security concerns for rendering use.
- **`markdown-it-py`** — parses Markdown text. Does not execute embedded code blocks. ReDoS resistance depends on version; keep updated.
- **`typing_extensions`** — type annotation utilities only. No runtime security surface.

---

## 8. Developer Internals

### 8.1 The Rich Console Protocol

This is the core extensibility mechanism. Any Python object can become a Rich renderable by implementing one or more of these methods:

**`__rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult`**

The primary protocol. Called by `Console.render()`. Must yield `Segment` or other renderable objects. Receives the calling Console and current `ConsoleOptions` (containing `max_width`, `min_width`, `encoding`, `justify`, `overflow`, `no_wrap`, `highlight`, `markup`, `height`, `is_alt_screen`).

```python
from rich.console import Console, ConsoleOptions, RenderResult
from rich.segment import Segment

class MyRenderable:
    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        yield Segment("Hello, ")
        yield Segment("World!", console.get_style("bold"))
        yield Segment.line()  # newline
```

**`__rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement`**

Optional. Returns the minimum and maximum width the renderable needs. Used by Table and other layout renderables to determine column widths. If not implemented, Console measures by trial-rendering at the maximum width.

```python
from rich.measure import Measurement

class MyRenderable:
    def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
        return Measurement(10, 40)  # min=10, max=40 cells
```

**`__rich__(self) -> RenderableType`**

Simplest protocol. Returns any renderable (string, Text, Table, etc.). Used when the object is not itself a renderable but can produce one.

```python
class User:
    def __rich__(self) -> str:
        return f"[bold]{self.name}[/bold] ({self.email})"
```

### 8.2 Segment — The Atomic Output Unit

`Segment` is a `NamedTuple` with three fields:

```python
class Segment(NamedTuple):
    text: str                         # The actual characters
    style: Optional[Style] = None     # Visual style
    control: Optional[Tuple[ControlType, ...]] = None  # Terminal control codes
```

Control segments (cursor movement, screen clearing) are distinct from text segments. The `Segment` class provides 24 static/class methods for stream manipulation:

- `Segment.line()` — newline segment
- `Segment.apply_style(segments, style)` — apply a style to a segment stream
- `Segment.filter_control(segments)` — remove control segments
- `Segment.split_lines(segments)` — split stream at newlines
- `Segment.set_shape(lines, width, height)` — pad/crop to exact dimensions
- `Segment.get_line_length(line)` — cell width of a line
- `Segment.get_shape(lines)` — measure width/height of line list
- `Segment.simplify(segments)` — merge adjacent segments with same style
- `Segment.strip_styles(segments)` — remove all styles
- `Segment.strip_links(segments)` — remove hyperlink styles
- `Segment.adjust_line_length(line, width)` — pad or crop to width
- `Segment.align_top/middle/bottom(lines, width, height, style)` — vertical alignment

### 8.3 Style System Internals

`Style` is immutable and combinable. Internally it stores attributes as a bitmask for fast comparison and combination:

```python
style = Style(bold=True, color="red")
style2 = Style(italic=True, bgcolor="blue")
combined = style + style2  # bold red on blue, italic
```

The `_Bit` descriptor class maps attribute names to bit positions in `_set_attributes` and `_attributes` integers. `_set_attributes` tracks which attributes are explicitly set (vs. inherited), enabling proper style stacking where a child style only overrides what it explicitly declares.

**Style parsing:** `Style.parse("bold red on blue underline")` tokenizes the string and maps keywords to style attributes. Color names, hex codes (`#ff0000`), and RGB (`rgb(255,0,0)`) are supported. Named styles are resolved through the Console's `ThemeStack`.

**Style caching:** `Style` objects are cached via `__hash__`. Console maintains a style cache (`_style_cache`) that maps style strings to resolved `Style` objects, avoiding repeated parsing.

### 8.4 Text Internals

`Text` is Rich's most complex class (54 methods). Key internals:

- **Spans:** Text stores formatting as a list of `Span(start, end, style)` tuples, not inline markup. This allows O(1) style application and efficient slicing.
- **Cell length tracking:** `Text._length` caches the cell width. Invalidated on mutation.
- **Wrapping:** `Text.wrap()` delegates to `_wrap.divide()` which finds word boundaries using Unicode line-break properties, then returns indices where breaks should occur.
- **Highlighting:** `Text.highlight_regex()` and `Text.highlight_words()` add spans without modifying the underlying text string.
- **Fitting:** `Text.fit(width)` returns a list of `Text` lines, each no wider than `width` cells.

### 8.5 Table Layout Algorithm

Table column sizing follows this algorithm (in `Table._calculate_column_widths()`):

```
1. Measure all cells in each column → (min_width, max_width) per column
2. Add column padding to measurements
3. If total max_width <= available_width:
     Use max_width for all columns (content fits)
4. Else: use ratio_reduce() to shrink columns proportionally
     Each column shrinks from its max toward its min
     Columns with ratio=0 are fixed and don't shrink
5. Columns with explicit width= are honored exactly
6. Columns with ratio=N get N proportional shares of remaining space
7. ratio_distribute() handles integer rounding (distributes remainders)
```

The layout math lives in `_ratio.py`:
- `ratio_resolve(total, ratios)` — distribute `total` among ratio-weighted items
- `ratio_reduce(total, ratios, maximums, values)` — reduce values to fit in total
- `ratio_distribute(total, ratios, minimums)` — distribute total respecting minimums

### 8.6 Progress Architecture

`Progress` extends `Live` and manages a list of `Task` objects:

```
Progress
  ├── Live (handles screen refresh via _RefreshThread)
  │     └── Console (actual output)
  ├── tasks: List[Task]  (each has id, total, completed, fields)
  ├── columns: List[ProgressColumn]  (define the visual layout)
  └── make_tasks_table() → Table (called on each refresh)
```

The 14 built-in `ProgressColumn` subclasses:

| Column Class | Renders |
|---|---|
| `TextColumn` | Static or templated text |
| `BarColumn` | The progress bar itself |
| `SpinnerColumn` | Animated spinner |
| `TimeElapsedColumn` | Time since task started |
| `TimeRemainingColumn` | Estimated time to completion |
| `FileSizeColumn` | Current bytes as human-readable |
| `TotalFileSizeColumn` | Total bytes as human-readable |
| `DownloadColumn` | Current/total download progress |
| `TransferSpeedColumn` | Transfer speed |
| `MofNCompleteColumn` | "M/N" counter |
| `TaskProgressColumn` | Percentage text |
| `RenderableColumn` | Arbitrary renderable |

Custom columns extend `ProgressColumn` and implement `render(task: Task) -> RenderableType`.

### 8.7 Live Display Mechanism

`Live` uses a background thread (`_RefreshThread`) that calls `Live.refresh()` at the configured `refresh_per_second` rate. The refresh cycle:

```
_RefreshThread.run():
    while not event.wait(1/refresh_per_second):
        Live.refresh()

Live.refresh():
    1. Get renderable from get_renderable()
    2. Render to segments via Console
    3. Compute diff from previous render (lines changed)
    4. Move cursor up to top of live region
    5. Overwrite changed lines
    6. Clear any extra lines from previous (longer) render
```

`Live` supports two modes:
- **Inline** (default): Renders below the current cursor position, rewriting in place
- **Alternate screen** (`screen=True`): Uses the terminal's alternate screen buffer

### 8.8 Markup Parser

`rich/markup.py` implements a BBCode-like tag parser. The `render()` function:

```
Input:  "[bold red]Hello [italic]world[/italic]![/bold red]"

Parse:  TAG_OPEN("bold red"), TEXT("Hello "), TAG_OPEN("italic"), 
        TEXT("world"), TAG_CLOSE("italic"), TEXT("!"), TAG_CLOSE("bold red")

Output: Text("Hello world!") with spans:
          Span(0, 12, "bold red")
          Span(6, 11, "italic")
```

Supported tag syntax:
- `[style]text[/style]` — named close
- `[style]text[/]` — auto-close innermost
- `[link=url]text[/link]` — hyperlinks (OSC 8)
- `[@click=handler]text[/@click]` — event callbacks (Textual integration)
- `[not bold]` — explicit negation
- Backslash escaping: `\[not a tag\]`

### 8.9 Color Implementation

`Color` supports multiple color specifications:

```python
Color.parse("red")              # Named (16 standard + X11 colors)
Color.parse("#ff0000")           # Hex
Color.parse("rgb(255,0,0)")      # RGB tuple
Color.parse("color(196)")        # xterm-256 palette index
Color.default()                  # Terminal default
```

Color downsampling (truecolor → 256 → 16) uses `Palette.match()` which computes Euclidean distance in RGB space against the target palette. The `_palettes.py` module contains the standard 16-color and 256-color palette data.

### 8.10 Code Patterns and Conventions

**Protocol-driven design:** Rich uses Python protocols (`__rich_console__`, `__rich_measure__`, `__rich__`) rather than class inheritance for extensibility. This means any object — even from third-party libraries — can be made renderable without subclassing.

**Immutable value objects:** `Style`, `Segment`, `Measurement`, `Color` are all immutable (or effectively immutable NamedTuples). This enables safe caching and sharing.

**String-based style specification:** Throughout the codebase, `StyleType = Union[str, Style]` is accepted. Strings are lazily parsed via `Console.get_style()`. This makes the API ergonomic (`"bold red"`) while remaining efficient (parsed once, then cached).

**`@rich.repr.auto` decorator:** Applied to Rich's own classes to generate `__repr__` methods that use `__rich_repr__()` for both standard and Rich-formatted output. Classes yield `(key, value)` or `(key, value, default)` tuples from `__rich_repr__()`.

**Overflow handling:** All text rendering supports three overflow modes — `fold` (break mid-word), `crop` (truncate), `ellipsis` (truncate with `...`). Controlled via `ConsoleOptions.overflow`.

**`_cell_len` caching:** The `Text` class caches cell-width calculations aggressively. The `cell_len()` function itself is optimized with a fast path for ASCII-only strings.

**Conditional Windows code:** Windows-specific modules (`_win32_console.py`, `_windows.py`, `_windows_renderer.py`) are only imported on Windows via conditional imports in `console.py`. The `legacy_windows` flag gates this entire code path.

---

## 9. Integration Points

### 9.1 Python Standard Library Integration

| stdlib Module | Rich Integration | Entry Point |
|---|---|---|
| `logging` | `RichHandler` replaces default handler | `rich.logging.RichHandler` |
| `sys.excepthook` | `install()` replaces exception hook | `rich.traceback.install()` |
| `sys.displayhook` | `install()` replaces REPL display | `rich.pretty.install()` |
| `warnings` | Can be rendered through Console | Via `Console.print()` |
| `io.StringIO` | Console writes to any `IO[str]` | `Console(file=StringIO())` |
| `subprocess` | `SystemPager` uses `subprocess.Popen` | `Console.pager()` |

### 9.2 IPython / Jupyter Integration

- `%load_ext rich` — loads the Rich IPython extension (`_extension.py`), which calls `pretty.install()` and `traceback.install()`
- `JupyterMixin` — base class providing `_repr_mimebundle_()` that renders Rich output as HTML in Jupyter cells
- Auto-detection: Console checks for `ipywidgets` availability and active Jupyter kernel

### 9.3 Pygments Integration

Rich wraps Pygments for syntax highlighting in `rich/syntax.py`:

- `Syntax` accepts any Pygments lexer name or auto-detects from filename
- `PygmentsSyntaxTheme` wraps Pygments themes (e.g., `"monokai"`, `"dracula"`, `"github-dark"`)
- `ANSISyntaxTheme` provides a 16-color alternative that doesn't use Pygments themes
- Token styles are converted from Pygments `Token.*` types to Rich `Style` objects

### 9.4 Textual Framework Integration

Rich is the rendering engine for [Textual](https://github.com/Textualize/textual), Textual's TUI framework. Integration points:

- `RenderHook` — Console supports render hooks that Textual uses to intercept and redirect output
- `@click` markup tags — parsed by Rich, handled by Textual's event system
- `Segment` — Textual's screen buffer is built from Rich Segments
- `Strip` — Textual extends Rich's rendering with its own Strip class (list of Segments with cached width)

### 9.5 Third-Party Ecosystem

Libraries that integrate with Rich (non-exhaustive, from issue tracker and community):

- **Click** — `from rich.click import rich_click` (via `rich-click` package)
- **pytest** — `pytest-rich` for test output formatting
- **Django** — Rich tracebacks via `RICH_TRACEBACK=1` (community package)
- **FastAPI / Uvicorn** — RichHandler for server logs
- **Typer** — uses Rich for all output formatting

### 9.6 Extending Rich

**Custom Renderable:**

```python
from rich.console import Console, ConsoleOptions, RenderResult
from rich.measure import Measurement
from rich.segment import Segment
from rich.style import Style

class HeatMap:
    def __init__(self, data: list[list[float]]):
        self.data = data
    
    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        for row in self.data:
            for value in row:
                r = int(value * 255)
                style = Style(bgcolor=f"rgb({r},0,{255-r})")
                yield Segment("  ", style)
            yield Segment.line()
    
    def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
        width = len(self.data[0]) * 2 if self.data else 0
        return Measurement(width, width)
```

**Custom Highlighter:**

```python
from rich.highlighter import RegexHighlighter

class LogHighlighter(RegexHighlighter):
    base_style = "log."
    highlights = [
        r"(?P<timestamp>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})",
        r"(?P<level>ERROR|WARN|INFO|DEBUG)",
        r"(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})",
    ]
# Requires theme styles: "log.timestamp", "log.level", "log.ip"
```

**Custom Progress Column:**

```python
from rich.progress import ProgressColumn, Task
from rich.text import Text

class GPUColumn(ProgressColumn):
    def render(self, task: Task) -> Text:
        gpu_pct = task.fields.get("gpu", 0)
        return Text(f"GPU: {gpu_pct}%", style="bold cyan")
```

### 9.7 Known Issues and Caveats for Integrators

| Issue | Details | Status |
|---|---|---|
| #4071 | Legacy Windows layer always uses stdout handle even when Console targets stderr | Open |
| #4028 | `ConsoleOptions` formatting overrides (justify, overflow) not propagated to nested renderables in all cases | Open |
| #3887 | Highlighter applied by Console may conflict with highlighter set on RichHandler, producing double-highlighting | Open |
| #2993 | `Text.markup` property fails to properly escape backslashes when reconstructing markup from spans | Open |
| #2073 | Pretty-printed items with multi-line `__repr__` are not indented correctly at depth > 1 | Open |
| #3820 | `Panel(Columns(...), expand=False)` ignores `expand=False` — Panel always expands to full width | Open |
| #3701 | `console.print()` with zero arguments does not print a blank line (differs from `builtins.print()` behavior) | Open |

---

_Generated May 2026 from Textualize/rich at 46cebbb. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
