# How Windows Terminal Handles Tabs and Panes: A Deep Dive into the Architecture

> Explore the Windows Terminal architecture and discover how it efficiently manages tabs and panes using a hierarchical tree structure for enhanced terminal productivity.

- Repository: [Microsoft/terminal](https://github.com/microsoft/terminal)
- Tags: deep-dive
- Published: 2026-02-26

---

**Windows Terminal uses a hierarchical tree structure where each window contains a `TerminalPage` that owns multiple `Tab` objects, and each `Tab` manages a binary tree of `Pane` objects that can host terminal content or further splits.**

The `microsoft/terminal` repository implements this architecture to support complex layouts with multiple terminals in a single window. Understanding how Windows Terminal handles tabs and panes reveals the design patterns behind its flexible UI, from simple single-pane tabs to nested grid layouts with dozens of terminals.

## The Core Hierarchy: TerminalPage, Tab, and Pane

The architecture follows a strict three-level ownership model defined in the source code:

```

TerminalPage
   └─ Tab (multiple, stored in std::vector<TerminalApp::Tab> _tabs)
          └─ Pane (root) ──► child Pane ──► … (binary tree)
                 │
                 └─ TerminalPaneContent (TermControl)

```

**`TerminalPage`** ([`src/cascadia/TerminalApp/TerminalPage.h`](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/TerminalPage.h)) serves as the top-level UI for each window. It maintains the tab collection in `std::vector<TerminalApp::Tab> _tabs` and provides methods like `_CreateNewTabFlyout` and `_UpdateTabIndices` to manage the tab strip.

**`Tab`** ([`src/cascadia/TerminalApp/Tab.h`](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/Tab.h) and [`Tab.cpp`](https://github.com/microsoft/terminal/blob/main/Tab.cpp)) acts as the logical container for a pane tree. Each `Tab` owns a `_rootPane` and tracks the `_activePane`. It exposes high-level operations including `SplitPane`, `NavigateFocus`, and `UpdateTitle` that delegate to the underlying pane tree.

**`Pane`** ([`src/cascadia/TerminalApp/Pane.h`](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/Pane.h) and [`Pane.cpp`](https://github.com/microsoft/terminal/blob/main/Pane.cpp)) represents a node in a binary split tree. A pane can be a **leaf** (holding content via `TerminalPaneContent`/`TermControl`) or a **branch** (containing `_firstChild` and `_secondChild` with a `SplitState` orientation). Key methods include `_IsLeaf()`, `WalkTree`, and `NavigateDirection`.

## Tab Creation and Lifecycle

When a user opens a new tab via the **+** button, `Ctrl+Shift+T`, or the "New Tab" dropdown, `TerminalPage::_CreateNewTabFromPane` executes the creation sequence:

1. Construct a **root `Pane`** containing the requested content (a new `TermControl` or settings pane).
2. Build a `Tab` object wrapping this root pane.
3. Register the tab with the `TerminalPage` and update the UI.

```cpp
auto rootPane = make_shared<Pane>(content, true);
auto tabImpl = winrt::make_self<Tab>(rootPane);
auto tab = winrt::TerminalApp::Tab{ tabImpl };

```

The `Tab` constructor assigns unique IDs to every leaf pane, selects the first pane as active, registers UI events, and creates its `TabViewItem` via `_MakeTabViewItem`. The tab title is driven by the active pane's `IPaneContent::Title()` (see `Tab::_GetActiveTitle`).

## The Binary Pane Tree and Splitting Logic

Pane splitting is the core mechanism enabling multiple terminals within a single tab. The operation is handled by `Tab::SplitPane` and implemented through the binary tree structure in `Pane`.

When a split is requested (e.g., `Ctrl+Shift+-` for horizontal):

1. **Walk the new pane tree** and attach event handlers (`_AttachEventHandlersToPane` / `_AttachEventHandlersToContent`).
2. **Save the ID** of the currently active pane (`_activePane->Id()`).
3. Call `Pane::Split` on the active pane, which creates a new internal node with `_firstChild` and `_secondChild` and assigns the split direction (`Horizontal` or `Vertical`).
4. The original active pane keeps its ID; the new pane receives a fresh ID from `_nextPaneId`.
5. `_UpdateActivePane` is called with the newly created leaf pane so UI focus follows the split.

```cpp
auto [original, newPane] = _activePane->Split(splitType, splitSize, pane);
_activePane = original;                     // keep focus on the original side
_UpdateActivePane(newPane);                 // give focus to the newly-added side

```

The split logic lives in `Pane::_Split` and uses `SplitState` to remember orientation. Size calculations (`CalcSnappedDimension`, `PreCalculateCanSplit`) ensure consistent behavior on high-DPI displays.

## Focus Navigation and Pane Activation

Moving focus between panes uses directional navigation that respects the binary tree geometry. `Tab::NavigateFocus` forwards a focus direction (Up/Down/Left/Right) to the root pane, which recursively searches for the neighboring leaf:

```cpp
if (const auto newFocus = _rootPane->NavigateDirection(_activePane, direction, _mruPanes))
{
    _changingActivePane = true;
    const auto res = _rootPane->FocusPane(newFocus);
    _changingActivePane = false;
    return res;
}

```

`Pane::NavigateDirection` uses `PaneNeighborSearch` (a helper struct) to locate the adjacent leaf based on split geometry. When a pane gains focus, its `GotFocus` event triggers `Tab::_UpdateActivePane`, which updates the UI (title, tab color, taskbar state) and fires `Tab::ActivePaneChanged`.

## Advanced Pane Operations

### Pane Swapping

`Tab::SwapPane` finds the neighbor pane in the requested direction and calls `Pane::SwapPanes` to exchange their positions in the tree without destroying content.

### Resizing

`Tab::ResizePane` forwards a `ResizeDirection` (e.g., `ResizeDirection::IncreaseWidth`) to `Pane::ResizePane`, which moves the separator between two children by adjusting their relative sizes.

### Zoom

`Tab::ToggleZoom` sets `_zoomedPane` and replaces the tab's content with the zoomed pane's `Grid`. Exiting zoom restores the original root pane layout.

All these operations rely on the binary pane-tree structure; they manipulate relationships between siblings rather than absolute screen coordinates.

## Tab-Wide State Aggregation

A `Tab` aggregates information from all its leaf panes to present a unified UI:

* **Title** – `Tab::_GetActiveTitle` prefers a user-set runtime title, otherwise falls back to the active pane's content title via `IPaneContent::Title()`.
* **Icon / Tab Color** – Updated via `Tab::_RecalculateAndApplyTabColor` whenever any leaf fires `TabColorChanged`.
* **Taskbar Progress** – `Tab::_UpdateProgressState` collects each leaf's `TaskbarState` via `Pane::CollectTaskbarStates` and selects the most severe according to Windows API priority rules.
* **Bell Indicator** – A `TermControl` raises `BellRequested`; the tab shows a visual bell and may flash the taskbar.

## Closing and Detaching Panes

* **Closing a pane** – `Tab::ClosePane` forwards to the active leaf's `Close()`. If the leaf is the only pane left, the `Tab` raises `Closed`, and `TerminalPage` removes the tab.
* **Detaching a pane** – `Tab::DetachPane` removes the active leaf from the tree via `Pane::DetachPane` and promotes its sibling to fill the space.
* **Closing a tab** – `Tab::Close` raises its `Closed` event; `TerminalPage::_HandleCloseTabRequested` removes the UI element and disposes the root pane.

## Code Examples

### Create a New Tab with a Custom Profile

```cpp
using namespace winrt::Microsoft::Terminal::Settings::Model;

// Build the profile arguments
auto profile = Profile{};
profile.Guid(guid_of_my_profile);
auto args = NewTabArgs{ winrt::make<NewTerminalArgs>(profile) };

// Ask the action dispatcher to open the tab
winrt::Microsoft::Terminal::Control::ShortcutActionDispatch dispatch = …;
dispatch.DoAction(tab, ShortcutAction::NewTab, args);

```

*Source:* `Tab::BuildStartupActions` (creates the `NewTab` action) – see [`Tab.cpp`](https://github.com/microsoft/terminal/blob/main/Tab.cpp) line 55‑57.

### Split the Active Pane Horizontally at 30 %

```cpp
// In a key-binding handler (e.g. Ctrl+Shift+-)
winrt::Microsoft::Terminal::Settings::Model::SplitDirection direction = SplitDirection::Horizontal;
float splitSize = 0.3f;               // 30 % of the current pane width

tab.SplitPane(direction, splitSize, nullptr); // `nullptr` creates a fresh terminal pane

```

*Source:* `Tab::SplitPane` – [`Tab.cpp`](https://github.com/microsoft/terminal/blob/main/Tab.cpp) lines 160‑168.

### Move Focus to the Pane on the Right

```cpp
tab.NavigateFocus(FocusDirection::Right);

```

*Source:* `Tab::NavigateFocus` – [`Tab.cpp`](https://github.com/microsoft/terminal/blob/main/Tab.cpp) lines 658‑666.

### Zoom the Active Pane

```cpp
tab.ToggleZoom();   // toggles between full-screen pane and normal layout

```

*Source:* `Tab::ToggleZoom` (delegates to `Pane::Maximize` / `Pane::Restore`) – see [`Tab.cpp`](https://github.com/microsoft/terminal/blob/main/Tab.cpp) around line 80.

### Change the Tab’s Title from Code

```cpp
tab.SetTabText(L"My Custom Title");

```

*Source:* `Tab::SetTabText` – [`Tab.cpp`](https://github.com/microsoft/terminal/blob/main/Tab.cpp) line 73‑77.

## Key Source Files

| File | Description |
|------|-------------|
| [[`TerminalPage.h`](https://github.com/microsoft/terminal/blob/main/TerminalPage.h)](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/TerminalPage.h) | Window-level container; creates tabs and handles tab-strip events. |
| [[`Tab.h`](https://github.com/microsoft/terminal/blob/main/Tab.h)](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/Tab.h) | Declaration of the `Tab` class managing pane-tree state and UI actions. |
| [[`Tab.cpp`](https://github.com/microsoft/terminal/blob/main/Tab.cpp)](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/Tab.cpp) | Full implementation of tab logic including creation, splitting, and focus management. |
| [[`Pane.h`](https://github.com/microsoft/terminal/blob/main/Pane.h)](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/Pane.h) | Core split-tree node defining leaf vs. branch states and navigation helpers. |
| [[`Pane.cpp`](https://github.com/microsoft/terminal/blob/main/Pane.cpp)](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/Pane.cpp) | Algorithms for splitting, resizing, zooming, and recursive tree traversal. |
| [[`TerminalPaneContent.h`](https://github.com/microsoft/terminal/blob/main/TerminalPaneContent.h)](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/TerminalPaneContent.h) | Interface for leaf content hosting the actual terminal emulator (`TermControl`). |
| [[`TabRowControl.h`](https://github.com/microsoft/terminal/blob/main/TabRowControl.h)](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/TabRowControl.h) | XAML control rendering the visual tab header with title and close button. |
| [[`TaskbarState.h`](https://github.com/microsoft/terminal/blob/main/TaskbarState.h)](https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalApp/TaskbarState.h) | Helper mapping per-pane progress states to Windows taskbar priority rules. |

## Summary

- **Windows Terminal handles tabs and panes** through a strict three-tier hierarchy: `TerminalPage` manages the window, `Tab` owns a binary tree of `Pane` objects, and leaf panes host `TerminalPaneContent`.
- **Pane splitting** creates a binary tree structure where each internal node stores `_firstChild` and `_secondChild`, enabling recursive navigation and resizing without absolute coordinate tracking.
- **Focus management** uses directional navigation (`NavigateFocus`) that traverses the pane tree to find geometric neighbors based on split orientation.
- **State aggregation** happens at the tab level, where `Tab` collects title, color, progress, and bell events from all leaf panes to update the UI and Windows taskbar.
- **Key implementation files** are located in `src/cascadia/TerminalApp/`, with [`Tab.cpp`](https://github.com/microsoft/terminal/blob/main/Tab.cpp) and [`Pane.cpp`](https://github.com/microsoft/terminal/blob/main/Pane.cpp) containing the core logic for layout management.

## Frequently Asked Questions

### How does Windows Terminal store the relationship between split panes?

Windows Terminal stores split panes as a **binary tree** where each `Pane` object acts as a node. If a pane is split, it becomes a branch node containing `std::shared_ptr<Pane> _firstChild` and `_secondChild` along with a `SplitState` indicating horizontal or vertical orientation. Leaf nodes host the actual terminal content via `TerminalPaneContent`. This tree structure allows the application to perform recursive operations like navigation, resizing, and zooming without tracking absolute screen coordinates.

### What happens when I close a pane that has been split?

When you close a pane, `Tab::ClosePane` forwards the request to the active leaf's `Close()` method. If the closed pane is the last leaf in its branch, the parent pane is removed from the tree and its sibling pane is promoted to fill the space via `Pane::DetachPane`. If the closed pane was the only pane remaining in the tab, the `Tab` raises a `Closed` event, causing `TerminalPage` to remove the entire tab from the window.

### How does focus navigation work between panes?

Focus navigation uses geometric direction rather than tab order. When you press `Alt+ArrowKey`, `Tab::NavigateFocus` calls `Pane::NavigateDirection` on the root pane with the current active pane and direction (Up, Down, Left, or Right). The method uses a `PaneNeighborSearch` helper to recursively traverse the binary tree and find the leaf pane that is geometrically adjacent in the requested direction based on the split orientations stored in `SplitState`. Once found, `Tab::_UpdateActivePane` updates the UI state and fires the `ActivePaneChanged` event.

### Can I programmatically control pane zoom and resizing?

Yes, the `Tab` class exposes methods to control zoom and resizing programmatically. Calling `Tab::ToggleZoom()` sets `_zoomedPane` and replaces the tab's content with the zoomed pane's `Grid`, delegating to `Pane::Maximize` and `Pane::Restore` to handle the layout switch. For resizing, `Tab::ResizePane` accepts a `ResizeDirection` (such as `IncreaseWidth`) and forwards it to `Pane::ResizePane`, which adjusts the separator between two child panes by modifying their relative sizes in the binary tree.