# How Rich Handles Windows Console Differences Between Legacy Console and Windows Terminal

> Learn how Rich automatically handles Windows console differences. It detects VT support and switches between ANSI escape sequences for Windows Terminal and Win32 API rendering for legacy consoles.

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

---

**Rich automatically detects Virtual Terminal (VT) support via the Win32 API and switches between standard ANSI escape sequences for Windows Terminal and a specialized Win32 API rendering pipeline for legacy consoles.**

The Textualize/rich library provides seamless cross-platform terminal rendering, but Windows presents unique architectural challenges between the classic Win32 console and modern terminal emulators. Understanding how Rich handles Windows console differences ensures consistent styled output whether your users run legacy PowerShell or the latest Windows Terminal.

## Detecting Console Capabilities at Runtime

### Querying the Win32 API for VT Support

Rich determines terminal capabilities through `rich._windows.get_windows_console_features()`, which interrogates the console mode using the Windows `GetConsoleMode` API on the standard output handle. The detection specifically checks for the `ENABLE_VIRTUAL_TERMINAL_PROCESSING` flag (value 4) to determine if the environment supports ANSI escape sequences.

```python

# rich/_windows.py (excerpt)

def get_windows_console_features() -> WindowsConsoleFeatures:
    handle = GetStdHandle()
    console_mode = GetConsoleMode(handle)
    vt = bool(console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
    truecolor = vt and (win_version.major > 10 or
                        (win_version.major == 10 and win_version.build >= 15063))
    return WindowsConsoleFeatures(vt=vt, truecolor=truecolor)

```

*Source*: [rich/_windows.py](https://github.com/Textualize/rich/blob/master/rich/_windows.py)

### Windows Version and True Color Detection

Beyond VT detection, Rich evaluates the Windows version to determine true-color support. True-color (24-bit color) is only enabled when VT processing is available and the system runs Windows 10 build 15063 or later, or Windows 11.

## Legacy Windows Console Mode

When `ENABLE_VIRTUAL_TERMINAL_PROCESSING` is absent, Rich activates legacy mode through the `detect_legacy_windows()` helper invoked during `Console.__init__` in [`rich/console.py`](https://github.com/Textualize/rich/blob/main/rich/console.py).

### The legacy_windows Flag and Width Adjustment

Setting `self.legacy_windows = True` triggers specific behavioral changes in the rendering pipeline. Most notably, Rich reduces the console width by exactly one column because the legacy console treats the final column as a trigger for automatic scrolling.

```python

# rich/console.py (excerpt)

self.legacy_windows: bool = (
    (detect_legacy_windows() and not self.is_jupyter)
    if legacy_windows is None
    else legacy_windows
)
if self.legacy_windows:
    # Width is one column narrower because the legacy console

    # treats the last column specially.

    width = int(columns) - self.legacy_windows

```

*Source*: [rich/console.py](https://github.com/Textualize/rich/blob/master/rich/console.py)

### Win32 API Rendering Pipeline

Instead of emitting ANSI sequences, Rich diverts output to `rich._windows_renderer.legacy_windows_render()`. This function iterates over `Segment` objects and translates them into Win32 API calls via the `LegacyWindowsTerm` class defined in [`rich/_win32_console.py`](https://github.com/Textualize/rich/blob/main/rich/_win32_console.py).

```python

# rich/_windows_renderer.py

def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None:
    for text, style, control in buffer:
        if not control:
            term.write_styled(text, style) if style else term.write_text(text)
        else:
            # translate ControlCodes into Win32 API calls

            ...

```

*Source*: [rich/_windows_renderer.py](https://github.com/Textualize/rich/blob/master/rich/_windows_renderer.py)

The `LegacyWindowsTerm` class utilizes native functions like `WriteConsole` and `SetConsoleTextAttribute` to apply colors and styles, while cursor movement operations use `SetConsoleCursorPosition`.

## Windows Terminal and Modern VT Mode

### Standard ANSI Escape Sequence Rendering

When VT processing is detected (`vt=True`), `Console.legacy_windows` remains `False` and Rich follows its standard rendering path. The console builds `Segment` objects containing raw ANSI escape codes and writes them directly to `sys.stdout` without Win32 API intermediation.

```python

# rich/console.py (excerpt, around line 2060)

use_legacy_windows_render = False
if self.legacy_windows:
    use_legacy_windows_render = True

if use_legacy_windows_render:
    from rich._windows_renderer import legacy_windows_render
    legacy_windows_render(buffer, LegacyWindowsTerm(self.file))
else:
    # Normal ANSI‑escape rendering

    self.file.write(...escape sequences...)

```

*Source*: [rich/console.py](https://github.com/Textualize/rich/blob/master/rich/console.py)

### Full Unicode and True Color Support

Modern terminals receive full-featured output including 24-bit true-color (when the Windows version permits), Unicode box-drawing characters, and emoji support. Components like `Tree`, `ProgressBar`, and `Box` consult `options.legacy_windows` to bypass these features in legacy environments, falling back to ASCII equivalents.

## Working with Console Modes in Practice

### Detecting the Mode at Runtime

You can inspect the `legacy_windows` attribute to determine which rendering path Rich selected:

```python
from rich.console import Console

console = Console()
if console.legacy_windows:
    console.print("[bold red]Running in legacy Windows console[/]")
else:
    console.print("[bold green]Running in a VT‑enabled terminal[/]")

```

### Forcing Legacy Mode for Testing

To test fallback behavior on a VT-capable terminal, explicitly enable legacy mode:

```python

# Force legacy mode even on a VT‑capable terminal

console = Console(legacy_windows=True)

# The table will fall back to ASCII box characters and no true‑color.

console.print(
    "[table]\n"
    "Header 1 | Header 2\n"
    "--------+--------\n"
    "value 1 | value 2\n"
    "[/table]"
)

```

### Observing Feature Differences

Features automatically adapt their output based on the console mode. Progress bars use ASCII blocks in legacy mode but switch to Unicode characters and smoother animations in VT mode:

```python
from rich.progress import Progress

# Progress bar will use ASCII blocks in legacy mode,

# but Unicode/ANSI colors in VT mode.

with Progress() as progress:
    task = progress.add_task("Processing...", total=100)
    for i in range(100):
        progress.update(task, advance=1)

```

### Direct Low-Level Legacy Rendering

For advanced scenarios, you can bypass the standard console write path and invoke the legacy renderer directly:

```python
from rich.console import Console
from rich.segment import Segment
from rich._windows_renderer import legacy_windows_render
from rich._win32_console import LegacyWindowsTerm

console = Console()
buffer = [Segment("Hello ", style=None), Segment("World!", style="bold red")]
term = LegacyWindowsTerm(console.file)

# Bypass the normal console write path

legacy_windows_render(buffer, term)

```

## Summary

- **Automatic Detection**: Rich queries `GetConsoleMode` via `rich._windows.get_windows_console_features()` to check for `ENABLE_VIRTUAL_TERMINAL_PROCESSAL_PROCESSING` (value 4).
- **Dual-Path Rendering**: The `legacy_windows` boolean flag in [`rich/console.py`](https://github.com/Textualize/rich/blob/main/rich/console.py) routes output either through standard ANSI sequences or the Win32 API-based `legacy_windows_render()` function.
- **Width Compensation**: Legacy mode automatically reduces console width by one column to prevent unwanted scrolling behavior in the classic console.
- **Feature Fallback**: When `legacy_windows` is `True`, Rich substitutes Unicode box drawing and true-color with ASCII equivalents and 16-color palette limits.
- **Zero Configuration**: The library requires no manual intervention; it detects capabilities during `Console` initialization and selects the appropriate renderer.

## Frequently Asked Questions

### How does Rich detect if it is running in Windows Terminal versus the legacy console?

Rich calls `get_windows_console_features()` from [`rich/_windows.py`](https://github.com/Textualize/rich/blob/main/rich/_windows.py), which uses the Win32 API function `GetConsoleMode` to inspect the standard output handle. If the `ENABLE_VIRTUAL_TERMINAL_PROCESSING` flag (value 4) is present, Rich assumes VT support and disables legacy mode. If the flag is absent, `detect_legacy_windows()` returns `True`, triggering the Win32 API rendering path.

### Why does Rich reduce the console width by one column in legacy mode?

The classic Windows console treats the final column as a special trigger for automatic scrolling when characters are written there. To prevent premature scrolling and layout corruption, [`rich/console.py`](https://github.com/Textualize/rich/blob/main/rich/console.py) subtracts one from the detected column count when `self.legacy_windows` is `True` (`self.width = columns - self.legacy_windows`).

### Can I force Rich to use legacy rendering even on Windows Terminal?

Yes. Instantiate `Console` with the `legacy_windows=True` parameter to override automatic detection. This forces Rich to use `legacy_windows_render()` from [`rich/_windows_renderer.py`](https://github.com/Textualize/rich/blob/main/rich/_windows_renderer.py) and the `LegacyWindowsTerm` class, which relies solely on Win32 API calls rather than ANSI escape sequences.

### What specific Win32 API functions does Rich use for legacy rendering?

The `LegacyWindowsTerm` class in [`rich/_win32_console.py`](https://github.com/Textualize/rich/blob/main/rich/_win32_console.py) wraps several native functions: `WriteConsole` for text output, `SetConsoleTextAttribute` for applying color and style attributes, and `SetConsoleCursorPosition` for cursor movement. These replace the ANSI escape codes used in modern terminals.