# How Rich Detects and Renders Output in Jupyter Notebooks: Inside the Textualize Source Code

> Discover how Rich detects and renders in Jupyter notebooks. Explore the Textualize source code to understand its automatic detection and HTML/ANSI rendering for styled output.

- Repository: [Textualize/rich](https://github.com/Textualize/rich)
- Tags: internals
- Published: 2026-03-06

---

**Rich automatically detects Jupyter environments using the `_is_jupyter()` helper in [`rich/console.py`](https://github.com/Textualize/rich/blob/main/rich/console.py) and renders styled HTML via the `JupyterMixin._repr_mimebundle_()` method in [`rich/jupyter.py`](https://github.com/Textualize/rich/blob/main/rich/jupyter.py), while falling back to ANSI terminal codes in standard IPython sessions.**

The Textualize/rich library solves the challenge of producing beautiful terminal output across different Python environments. When your code runs inside a Jupyter notebook, Google Colab, or Databricks, Rich detects this context and switches from ANSI escape sequences to HTML-based rendering, ensuring your tables, panels, and syntax highlighting remain visually consistent regardless of the runtime.

## Detecting a Jupyter Environment

Rich determines whether it is operating inside a Jupyter-compatible environment using environment inspection rather than simple module presence checks. This detection happens during `Console` instantiation and drives all subsequent rendering decisions.

### The `_is_jupyter()` Implementation

The private helper `_is_jupyter()` in [`rich/console.py`](https://github.com/Textualize/rich/blob/main/rich/console.py) performs the actual environment detection. It first checks for the presence of `get_ipython`, a builtin available in IPython contexts but absent in standard Python interpreters.

```python

# rich/console.py

def _is_jupyter() -> bool:  # pragma: no cover

    """Check if we're running in a Jupyter notebook."""
    try:
        get_ipython  # type: ignore[name-defined]

    except NameError:
        return False
    ipython = get_ipython()  # type: ignore[name-defined]

    shell = ipython.__class__.__name__
    if (
        "google.colab" in str(ipython.__class__)      # Colab

        or os.getenv("DATABRICKS_RUNTIME_VERSION")   # Databricks

        or shell == "ZMQInteractiveShell"            # Jupyter or qtconsole

    ):
        return True
    elif shell == "TerminalInteractiveShell":
        return False   # plain IPython terminal

    else:
        return False   # any other interpreter

```

The function specifically identifies **ZMQInteractiveShell** (standard Jupyter), **google.colab** classes, and **Databricks** runtimes via environment variables. It explicitly returns `False` for **TerminalInteractiveShell**, ensuring that plain IPython terminals receive ANSI output rather than HTML.

### Console Initialization Flag

When you instantiate a `Console`, the constructor calls `_is_jupyter()` unless you explicitly override detection with the `force_jupyter` parameter.

```python

# rich/console.py – inside Console.__init__

self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter

```

This boolean flag (`self.is_jupyter`) determines the entire rendering pipeline. When `True`, Rich prepares MIME bundles for IPython's display system; when `False`, it writes ANSI escape codes directly to `sys.stdout`.

## The Jupyter Rendering Pipeline

Rich provides two complementary mechanisms for Jupyter output defined in [`rich/jupyter.py`](https://github.com/Textualize/rich/blob/main/rich/jupyter.py): the `JupyterMixin` protocol integration and the low-level `display()` helper.

### JupyterMixin and the `_repr_mimebundle_` Protocol

Most Rich renderables (such as `Table`, `Panel`, and `Syntax`) inherit from `JupyterMixin`, which supplies the `_repr_mimebundle_` method. IPython automatically calls this method when displaying objects in a notebook context.

The method performs four distinct operations:

1. Obtains the global console via `rich.get_console()`
2. Renders the object to a list of `Segment` objects using `console.render(self, console.options)`
3. Converts segments to HTML via `_render_segments()` and to plain text via `console._render_buffer()`
4. Returns a dictionary with `"text/plain"` and `"text/html"` keys

```python

# rich/jupyter.py – JupyterMixin

def _repr_mimebundle_(self, include, exclude, **kwargs):
    console = get_console()
    segments = list(console.render(self, console.options))
    html = _render_segments(segments)
    text = console._render_buffer(segments)
    data = {"text/plain": text, "text/html": html}
    return data

```

### Converting Segments to HTML with `_render_segments`

The `_render_segments` function transforms Rich's internal `Segment` objects into HTML markup. It processes simplified segments (via `Segment.simplify()`) to collapse adjacent styles, performs HTML escaping, and maps Rich styles to inline CSS.

```python

# rich/jupyter.py – _render_segments

def _render_segments(segments):
    def escape(text):  # HTML‑escape

        return text.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;")
    fragments = []
    theme = DEFAULT_TERMINAL_THEME
    for text, style, control in Segment.simplify(segments):
        if control:
            continue
        text = escape(text)
        if style:
            rule = style.get_html_style(theme)
            text = f'<span style="{rule}">{text}</span>' if rule else text
            if style.link:
                text = f'<a href="{style.link}" target="_blank">{text}</a>'
        fragments.append(text)
    code = "".join(fragments)
    return JUPYTER_HTML_FORMAT.format(code=code)

```

The function uses `Style.get_html_style(theme)` from [`rich/style.py`](https://github.com/Textualize/rich/blob/main/rich/style.py) to translate Rich's style attributes into CSS rules based on the `DEFAULT_TERMINAL_THEME`. Links are preserved as HTML anchor tags. The final markup is wrapped in a `<pre>` element (via `JUPYTER_HTML_FORMAT`) to preserve whitespace and use monospace fonts.

### The Low-Level `display()` Helper

For scenarios where you already have a list of `Segment` objects, `rich.jupyter.display()` provides direct access to the rendering pipeline without requiring a renderable object.

```python

# rich/jupyter.py – display()

def display(segments, text):
    html = _render_segments(segments)
    jupyter_renderable = JupyterRenderable(html, text)
    from IPython.display import display as ipython_display
    ipython_display(jupyter_renderable)

```

This function creates a `JupyterRenderable` instance (a thin wrapper carrying HTML and text representations) and passes it to IPython's display system. If IPython is not installed, the function silently returns without raising errors, guarding against environments where `force_jupyter=True` but the IPython library is absent.

## Practical Implementation Examples

### Automatic Detection in Notebooks

The standard usage pattern relies on Rich's automatic detection. You instantiate a `Console` and print renderables normally; the output automatically appears as styled HTML in notebook cells.

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

console = Console()  # Automatically detects Jupyter

table = Table(title="Demo")
table.add_column("Name")
table.add_column("Age")
table.add_row("Alice", "30")
table.add_row("Bob", "25")

console.print(table)  # Renders as HTML in Jupyter, ANSI in terminal

```

### Manual Segment Rendering

For advanced use cases, you can manually render content to segments and display them explicitly using the low-level API.

```python
from rich.console import Console
from rich.jupyter import display

console = Console()
segments = list(console.render("Hello, [bold magenta]World![/]"))
display(segments, "Hello, World!")  # Explicit call to Jupyter display

```

### Forcing Terminal Mode

To override the automatic detection and force standard terminal rendering (ANSI escape codes) even inside a notebook, set `force_jupyter=False`.

```python
from rich.console import Console

# Force ANSI output even in Jupyter

console = Console(force_jupyter=False)
console.print("[red]This appears as raw ANSI escape codes[/]")

```

## Summary

- **Detection logic** resides in [`rich/console.py`](https://github.com/Textualize/rich/blob/main/rich/console.py) via the `_is_jupyter()` function, which checks for `ZMQInteractiveShell`, Google Colab classes, and Databricks environment variables.
- **Rendering protocol** is implemented in [`rich/jupyter.py`](https://github.com/Textualize/rich/blob/main/rich/jupyter.py) through `JupyterMixin._repr_mimebundle_()`, which returns MIME bundles containing both HTML and plain text representations.
- **HTML generation** occurs in `_render_segments()`, which converts `Segment` objects (from [`rich/segment.py`](https://github.com/Textualize/rich/blob/main/rich/segment.py)) into HTML using CSS styles from `Style.get_html_style()` (defined in [`rich/style.py`](https://github.com/Textualize/rich/blob/main/rich/style.py)).
- **Fallback mechanism** ensures that if IPython is unavailable or `force_jupyter=False`, Rich degrades gracefully to standard terminal output using ANSI escape sequences.

## Frequently Asked Questions

### How does Rich distinguish between Jupyter and plain IPython?

Rich examines the IPython shell type via `get_ipython().__class__.__name__`. It returns `True` only for **ZMQInteractiveShell** (Jupyter's kernel protocol), specific Google Colab class names, or Databricks runtimes. It explicitly returns `False` for **TerminalInteractiveShell**, which indicates a standard IPython terminal session that should receive ANSI codes rather than HTML.

### What MIME types does Rich return to Jupyter?

According to the `JupyterMixin._repr_mimebundle_()` implementation in [`rich/jupyter.py`](https://github.com/Textualize/rich/blob/main/rich/jupyter.py), Rich returns a dictionary containing two keys: `"text/plain"` containing the raw text representation, and `"text/html"` containing the styled HTML markup. Jupyter uses the HTML version for visual display while preserving the plain text for clipboard operations or text-only contexts.

### Can I force Rich to use HTML rendering outside of Jupyter?

Yes. While Rich automatically detects the environment, you can force Jupyter-style rendering by setting `force_jupyter=True` when creating a `Console` instance. However, this requires IPython to be installed in the environment, as the `display()` function and `_repr_mimebundle_` protocol depend on IPython's display system to actually render the HTML.

### How does Rich handle links in Jupyter output?

When processing segments in `_render_segments()`, Rich checks for the `style.link` attribute. If a link is present, it wraps the styled text in an HTML anchor tag with `target="_blank"` using the format `f'<a href="{style.link}" target="_blank">{text}</a>'`. This ensures clickable hyperlinks function correctly in the notebook's HTML output while remaining plain text in terminal fallback mode.