# Architecture of the Layer/Widget System for Modals and Popups in Telegram Desktop

> Explore Telegram Desktop's layer stack architecture for modals and popups. Learn how Ui::LayerStackWidget manages dialogs, animations, and input for a seamless user experience.

- Repository: [Telegram Desktop/tdesktop](https://github.com/telegramdesktop/tdesktop)
- Tags: architecture
- Published: 2026-04-05

---

**TL;DR:** Telegram Desktop implements all modal dialogs, pop-ups, and overlays using a centralized **layer stack architecture** built on `lib_ui` primitives, where `Ui::LayerStackWidget` manages a stack of `Ui::LayerWidget` objects that handle animation, backdrop rendering, and input blocking, while `Ui::BoxContent` provides ready-made centered dialogs.

The modal and popup system in Telegram Desktop relies on a sophisticated yet lightweight widget hierarchy defined in the `lib_ui` submodule. This architecture separates the concerns of **layer management**, **content presentation**, and **animation lifecycle**, allowing any component to spawn full-screen overlays or centered confirmation boxes with minimal boilerplate.

## Core Components of the Layer Architecture

### Ui::LayerWidget – The Foundation for Overlays

At the base of the system sits **`Ui::LayerWidget`**, defined in [`lib_ui/ui/layers/layer_widget.h`](https://github.com/telegramdesktop/tdesktop/blob/main/lib_ui/ui/layers/layer_widget.h). This class serves as the abstract foundation for any full-screen overlay. It encapsulates critical responsibilities including **animation state management**, **semi-transparent black-out background rendering**, **input event blocking**, and **lifetime management**. Every modal in Telegram Desktop, whether a simple confirmation or a complex media editor, ultimately inherits from or wraps this class.

### Ui::LayerStackWidget – The Centralized Stack Manager

The **`Ui::LayerStackWidget`**, located in [`lib_ui/ui/layers/layer_stack_widget.h`](https://github.com/telegramdesktop/tdesktop/blob/main/lib_ui/ui/layers/layer_stack_widget.h), acts as the single source of truth for modal state. The `MainWindow` owns exactly one instance via `base::unique_qptr<Ui::LayerStackWidget> _layer`. This stack widget guarantees that **only the topmost layer receives input events** and coordinates the black-out backdrop drawing across the entire window. When layers are pushed or popped, the stack manages the visual hierarchy and ensures proper focus restoration.

### Ui::BoxContent and Ui::GenericBox – Ready-Made Dialogs

For standard centered dialogs, the codebase uses **`Ui::BoxContent`** ([`lib_ui/ui/boxes/box_content.h`](https://github.com/telegramdesktop/tdesktop/blob/main/lib_ui/ui/boxes/box_content.h)) and its concrete subclass **`Ui::GenericBox`** ([`lib_ui/ui/boxes/generic_box.h`](https://github.com/telegramdesktop/tdesktop/blob/main/lib_ui/ui/boxes/generic_box.h)). These classes provide ready-to-use layouts with default paddings, scrollable bodies, title bars, and OK/Cancel button rows. Rather than constructing raw `LayerWidget` instances, most features use `GenericBox` for settings, confirmations, and form dialogs like "Edit profile" or "Delete chat".

## How the Layer Stack Works in Practice

### Ownership and Event Routing

The `MainWindow` instantiates a single layer stack during initialization:

```cpp
base::unique_qptr<Ui::LayerStackWidget> _layer;

```

When displaying a modal, the code creates a `LayerWidget`, attaches content, and pushes it onto this stack:

```cpp
auto layer = Ui::CreateChild<Ui::LayerWidget>(parent);
layer->setContent(object_ptr<Ui::BoxContent>(...));
_layer->pushLayer(std::move(layer), options);

```

The `LayerStackWidget` strictly enforces **event isolation**: only the topmost layer processes mouse and keyboard input. When that layer closes via `popLayer()`, the previous layer automatically regains focus and event handling privileges.

### Animation and Transitions

Animation behavior is controlled through **`Ui::LayerOptions`**, which carries flags such as `anim::type::normal`, `anim::type::instant`, and `Ui::LayerOption::CloseByClick`. The `LayerWidget` implementation utilizes Qt's `QPropertyAnimation` to execute slide and fade transitions. These options are passed during the `pushLayer()` call, ensuring consistent visual behavior across the application.

## Implementation Patterns: Boxes vs. Full-Screen Layers

### The Box Pattern (Centered Dialogs)

Most user interactions employ the box pattern for small-to-medium dialogs. These appear as centered cards with limited dimensions:

```cpp
auto box = Ui::CreateChild<Ui::GenericBox>(parent);
box->setTitle(tr::lng_confirm_delete_item(tr::now, lt_item_name, name));
Ui::show(std::move(box), Ui::LayerOption::CloseByClick);

```

Under the hood, the `Ui::show()` helper wraps the `GenericBox` in a `LayerWidget`, pushes it onto the stack, and applies the requested animation options.

### Full-Screen Layer Pattern

For immersive flows like the photo editor, story creation, or login sequences, the code creates **full-screen layers** with custom content:

```cpp
auto editor = Ui::CreateChild<Ui::LayerWidget>(parent);
editor->setContent(object_ptr<Editor::LayerWidget>(editor, args));
Ui::showLayer(std::move(editor), Ui::LayerOption::KeepOtherLayers);

```

Using `Ui::LayerOption::KeepOtherLayers` allows previous layers to remain visible underneath, which supports "undo" overlays or multi-step workflows without destroying the underlying state.

## Helper Functions and Global Access

The [`lib_ui/ui/layers/show.h`](https://github.com/telegramdesktop/tdesktop/blob/main/lib_ui/ui/layers/show.h) header provides convenience wrappers that eliminate the need to manually reference `MainWindow`:

- **`Ui::show()`** – General-purpose display helper
- **`Ui::showLayer()`** – For full-screen `LayerWidget` instances
- **`Ui::showBox()`** – Specifically for `BoxContent` subclasses

These functions resolve the active `LayerStackWidget` through `Ui::MainWidget::layerStack()`, enabling any controller or utility class to spawn modals without knowing the window hierarchy. For special cases requiring overlays without the black-out backdrop (such as media previews), `MainWindow` exposes `Ui::showSpecialLayer()`.

## Lifetime Management and Safety

All layers are owned by the `LayerStackWidget` via **`base::unique_qptr`**. This smart pointer implementation ensures automatic deletion once the closing animation completes, preventing memory leaks and guaranteeing **deterministic destruction order**. The stack deletes layers from top to bottom, ensuring that child widgets are destroyed before their parents.

Programmatic closure is handled through `MainWindow` methods:

```cpp
if (window->ui_isLayerShown()) {
    window->ui_hideSettingsAndLayer(anim::type::normal);
}

```

This invokes `LayerStackWidget::popLayer()`, triggers the reverse animation, and safely destroys the widget.

## Real-World Usage Examples

### Showing a Box from a Controller

When the calling code lacks direct access to `MainWindow`, the global helpers provide clean abstraction:

```cpp
Ui::showBox(box_ptr, Ui::LayerOption::CloseByClick);

```

### Complex Full-Screen Flows

The `iv::Instance` (Instant View) and `InfoLayerWidget` implementations demonstrate how the same infrastructure supports temporary UI flows over the main window. Similarly, `Editor::LayerWidget` in [`Telegram/SourceFiles/editor/editor_layer_widget.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/Telegram/SourceFiles/editor/editor_layer_widget.cpp) showcases a full-screen editor built entirely on the layer stack primitives.

## Summary

- **Centralized Stack**: A single `Ui::LayerStackWidget` owned by `MainWindow` manages all modal state.
- **Dual Patterns**: `Ui::GenericBox` provides centered dialogs, while custom `Ui::LayerWidget` subclasses handle full-screen flows.
- **Automatic Safety**: `base::unique_qptr` ownership ensures layers are deleted automatically after animations complete.
- **Global Access**: Helper functions in [`lib_ui/ui/layers/show.h`](https://github.com/telegramdesktop/tdesktop/blob/main/lib_ui/ui/layers/show.h) allow any component to spawn modals without window references.
- **Event Isolation**: The stack guarantees only the topmost layer receives input, with previous layers automatically regaining focus on closure.

## Frequently Asked Questions

### What is the difference between a LayerWidget and a BoxContent in Telegram Desktop?

**`Ui::LayerWidget`** is the low-level container that handles full-screen overlays, animations, and backdrops, while **`Ui::BoxContent`** is a higher-level abstraction specifically designed for centered, card-style dialogs with standardized layouts. In practice, `BoxContent` objects are almost always wrapped inside a `LayerWidget` before being displayed, as seen in the `Ui::show()` implementation.

### How does Telegram Desktop handle multiple simultaneous modals?

The `Ui::LayerStackWidget` maintains a **stack** of layers, pushing new modals on top and popping them on close. Only the topmost layer receives input events, and each layer can optionally keep previous layers visible underneath using the `KeepOtherLayers` option. This stack-based approach prevents focus conflicts and ensures predictable z-ordering.

### Where are the layer helper functions like Ui::show defined?

These global helpers are declared in [`lib_ui/ui/layers/show.h`](https://github.com/telegramdesktop/tdesktop/blob/main/lib_ui/ui/layers/show.h) and provide convenience wrappers around the layer stack mechanics. They automatically resolve the active `LayerStackWidget` through `Ui::MainWidget::layerStack()`, allowing developers to display modals from anywhere in the codebase without passing `MainWindow` pointers through the call chain.

### How can I programmatically close the topmost modal in Telegram Desktop?

Use the `MainWindow` inspection methods `ui_isLayerShown()` to check for active layers, then call `ui_hideSettingsAndLayer(anim::type::normal)` to trigger the close animation. This internally invokes `LayerStackWidget::popLayer()`, which runs the exit animation and automatically destroys the widget via `base::unique_qptr` when finished.