# How the Telegram Desktop Theme System Works: Loading Custom Themes and Color Schemes

> Explore the Telegram Desktop theme system Learn how custom themes load via tdesktop theme files, parse color schemes, and apply to your interface with real time adjustments.

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

---

**Telegram Desktop loads custom themes from `.tdesktop-theme` files by parsing color schemes and optional background images into a `style::palette`, applying them through a preview-then-persist workflow while supporting real-time file watching and system accent color adjustments.**

The Telegram Desktop theme system manages both built-in embedded themes and user-provided custom themes through a comprehensive C++ implementation. Located in the `telegramdesktop/tdesktop` repository, this system handles theme discovery, color adjustment, dynamic loading, and runtime switching including night mode transitions.

## Theme Architecture Overview

The implementation centers around three distinct concepts that work together to provide flexible theming:

- **Embedded themes** – Built-in themes shipped with the application (Default, Day-Blue, Night, Night-Green) stored as Qt resources in `window/themes/window_themes_embedded.*`
- **User-provided themes** – `.tdesktop-theme` files containing a color scheme (`colors.tdesktop-theme` or `colors.tdesktop-palette`) and optional background images handled by `window/themes/window_theme.*`
- **Theme preview and editor** – A temporary testing state that renders new palettes without persistence, managed by `window/themes/window_theme_editor.*` and `window/themes/window_theme_preview.*`

## Theme Discovery and Color Adjustment

When Telegram Desktop encounters a theme file, it first determines whether it is an embedded resource via `IsEmbeddedTheme(path)`. Embedded themes reside in Qt resources (`:/gui/...`) with fixed color schemes defined in `EmbeddedThemes()`.

For accent color customization, the system checks if the user has enabled system accent colors or selected a custom accent in Settings. The `ColorizerForTheme(path)` function creates a `style::colorizer` that remaps the theme's primary color while preserving a whitelist of keys (`kColorizeIgnoredKeys`). This colorizer passes through to the theme parser in [`window/themes/window_theme.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/window/themes/window_theme.cpp).

The relevant implementations reside in:
- [`window/themes/window_themes_embedded.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/window/themes/window_themes_embedded.cpp) – Contains `ColorizerFrom`, `SystemAccentColor`, `EmbeddedThemes()`, and `DefaultAccentColors()`
- [`window/themes/window_theme.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/window/themes/window_theme.cpp) – Implements `IsEmbeddedTheme`, `ColorizerForTheme`, `Apply`, and `LoadTheme`

## Loading Custom Themes from Files

The core loading mechanism resides in `Window::Theme::LoadFromFile`, which validates and parses theme archives:

```cpp
bool Window::Theme::LoadFromFile(
    const QString &path,
    not_null<Instance*> out,
    Cached *outCache,
    QByteArray *outContent) {
  const auto colorizer = ColorizerForTheme(path);
  return LoadFromFile(path, out, outCache, outContent, colorizer);
}

```

The loading process follows these steps:

1. **File validation** – `readThemeContent` reads the file and checks against `kThemeFileSizeLimit` (5 MiB)
2. **Archive extraction** – `LoadTheme` unpacks the `.zip` (or plain text) theme, extracting the color scheme (`colors.tdesktop-theme` or `colors.tdesktop-palette`) and optional background image (`background.jpg/png`)
3. **Color scheme parsing** – `loadColorScheme` parses the scheme line-by-line using `ReadPaletteValues` and populates a `style::palette`, optionally applying the `style::colorizer`
4. **Background processing** – If present, `loadBackground` extracts the image, validates dimensions against `kBackgroundSizeLimit`, and stores it in the `Instance` representing the parsed theme

## Applying and Persisting Themes

The public API exposes several entry points for theme application:

- **`Apply(const QString &filepath, const Data::CloudTheme &cloud)`** – Loads a theme file and initiates a testing session for preview
- **`ApplyDefaultWithPath(const QString &themePath)`** – Used for built-in default or night themes, creating a `Preview` from embedded resources
- **`KeepApplied()`** – Persists the temporary theme data when the user confirms a preview, writing to disk via `Local::writeTheme` and committing the background

During application, `ChatBackground::setTestingTheme` swaps the current palette through `style::main_palette::apply` and stores background images via `setThemeData` for UI rendering.

## File Watching and Live Reloading

For themes loaded from regular files (non-embedded), Telegram Desktop establishes a `QFileSystemWatcher` in `ChatBackground::refreshThemeWatcher`. When the file changes on disk, the watcher triggers:

```cpp
Apply(path);
KeepApplied();

```

This enables real-time theme development, allowing users to edit `.tdesktop-theme` files in external editors and see changes instantly without restarting the application.

## Night Mode Implementation

Night mode operates as a thin wrapper around the standard theme logic:

- `ToggleNightMode()` flips the boolean flag in `ChatBackground`
- `reapplyWithNightMode` determines whether to load the night embedded theme (`NightThemePath()`) or a user-provided variant
- The UI reacts to `IsNightModeValue()` (an `rpl::producer`) to update widgets automatically when the mode changes

## Theme Editor Integration

The theme editor (`window/themes/window_theme_editor_box.cpp/h`) constructs temporary `Preview` objects via `PreviewFromFile` and hands them to `Apply`. While the editor remains open, the theme operates in testing mode. `KeepFromEditor` writes the final theme to disk when the user saves, while `Revert` restores the previous palette if the user cancels the operation.

## Summary

- Telegram Desktop distinguishes between **embedded themes** (Qt resources) and **user-provided themes** (`.tdesktop-theme` files) through `IsEmbeddedTheme()`
- The `LoadFromFile` function enforces a 5 MiB size limit and extracts color schemes and backgrounds from ZIP archives
- **Testing mode** allows previewing themes before persistence via `Apply()` and `KeepApplied()`
- **Real-time reloading** works through `QFileSystemWatcher` for external file changes
- **Accent color adjustments** apply via `style::colorizer` while protecting whitelisted keys
- **Night mode** toggles between embedded themes using `ToggleNightMode()` and `reapplyWithNightMode`

## Frequently Asked Questions

### How does Telegram Desktop handle large theme files?

The system enforces `kThemeFileSizeLimit` (5 MiB) in [`window/themes/window_theme.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/window/themes/window_theme.cpp). Files exceeding this limit are rejected during the `readThemeContent` phase to prevent memory issues and ensure rapid parsing.

### Can I modify a theme while Telegram Desktop is running and see changes immediately?

Yes. For non-embedded themes, the application creates a `QFileSystemWatcher` in `ChatBackground::refreshThemeWatcher` that monitors the theme file path. When changes are detected, it automatically calls `Apply(path)` followed by `KeepApplied()` to reload and persist the updated theme without requiring a restart.

### What is the difference between `Apply()` and `KeepApplied()` in the theme system?

`Apply()` initiates a **testing session** that temporarily swaps the current palette and background for preview purposes without writing to persistent storage. `KeepApplied()` commits these changes permanently by writing the theme data to disk via `Local::writeTheme` and clearing the temporary state. This two-phase approach prevents incomplete or broken themes from corrupting the user's settings.

### How does the accent color customization work with existing themes?

When loading any theme, `ColorizerForTheme(path)` checks for system or user-defined accent colors. If present, it creates a `style::colorizer` through `ColorizerFrom` that remaps specific color values while respecting `kColorizeIgnoredKeys` (a whitelist of colors that should not be modified). This colorizer passes into `loadColorScheme` to adjust the palette during parsing, allowing themes like Day-Blue to adapt to user preferences while maintaining visual consistency for critical UI elements.