# How the Theme System Supports Light/Dark Mode and Custom Themes in UAD Next Generation

> Explore the Universal Android Debloater Next Generation's theme system. Discover how it seamlessly supports light/dark modes and custom themes with automatic OS color scheme detection and persistent user settings.

- Repository: [Universal-Debloater-Alliance/universal-android-debloater-next-generation](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation)
- Tags: internals
- Published: 2026-06-20

---

**The Universal Android Debloater Next Generation uses Iced's theme API combined with the `dark_light` crate to automatically detect OS color schemes, while providing built-in Light, Dark, and custom Lupin palettes that users can switch between at runtime with persistent settings.**

The theme system in Universal-Debloater-Alliance/universal-android-debloater-next-generation delivers a flexible, cross-platform appearance engine that respects system preferences while allowing granular customization. Built on the Rust Iced GUI framework, it automatically detects whether your operating system is set to light or dark mode and can instantly switch between predefined color palettes including a distinctive "Lupin" custom theme.

## OS-Level Color Scheme Detection

The foundation of automatic theme switching relies on the `dark_light` crate. In [`crates/uad-gui/src/theme.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/theme.rs), a static `LazyLock` caches the result of OS detection at startup:

```rust
pub static OS_COLOR_SCHEME: LazyLock<dark_light::Mode> =
    LazyLock::new(|| dark_light::detect().unwrap_or(dark_light::Mode::Unspecified));

```

This global static (lines 14-16) ensures that the detected mode—Dark, Light, or Unspecified—is computed once and reused throughout the application lifecycle, eliminating redundant system calls while providing the source of truth for the `Auto` theme variant.

## Theme Enumeration and Built-In Palettes

The `Theme` enum in [`theme.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/theme.rs) defines four supported variants that map to specific color palettes:

```rust
pub enum Theme {
    #[default] Auto,
    Lupin,
    Dark,
    Light,
}

```

Lines 17-29 establish **Auto** as the default, which defers to the OS color scheme detected via `OS_COLOR_SCHEME`, while **Lupin** provides a custom aesthetic distinct from standard light/dark modes. Each variant corresponds to a hard-coded `ColorPalette` struct containing base, normal, and bright color definitions (lines 71-135).

## Color Palette Implementation

Each theme variant returns its specific `ColorPalette` through the `palette()` method. The source code defines three constant palettes: `DARK`, `LIGHT`, and `LUPIN` (lines 72-125). When `palette()` executes, it matches the variant:

```rust
match self {
    Self::Auto => /* OS detection logic */,
    Self::Dark => DARK,
    Self::Light => LIGHT,
    Self::Lupin => LUPIN,
}

```

This architecture ensures that color values are compile-time constants, providing zero-cost runtime theme resolution while maintaining type safety. The `ColorPalette` struct contains nested `BaseColors`, `NormalColors`, and `BrightColors` that specify exact RGB values for every UI element.

## Iced Framework Integration

The theme system implements `iced::theme::Base` to integrate with the Iced widget rendering pipeline. In [`theme.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/theme.rs) (lines 164-190), three critical methods bridge the custom theme to Iced's internals:

- **`default(preference)`**: Maps Iced's `Mode` preference to a concrete theme, falling back to `OS_COLOR_SCHEME` when the preference is `None`
- **`mode(&self)`**: Resolves the effective `Mode` (Light/Dark) for the current theme, consulting `OS_COLOR_SCHEME` for the `Auto` variant
- **`palette(&self)`** and **`name(&self)`**: Expose the color values and identifier strings that Iced uses when drawing containers, buttons, and text

This implementation allows the application to react to system theme changes dynamically when running in `Auto` mode, ensuring widgets always render with appropriate contrast ratios.

## Runtime Theme Selection and Persistence

User interaction occurs in [`crates/uad-gui/src/views/settings.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/views/settings.rs). The Settings view constructs a radio-button list from `Theme::ALL`, providing one-click switching:

```rust
let radio_btn_theme = Theme::ALL
    .iter()
    .fold(row![].spacing(10), |column, option| {
        column.push(
            radio(
                format!("{}", option.clone()),
                *option,
                Some(string_to_theme(&self.general.theme)),
                Message::ApplyTheme,
            )
            .size(24),
        )
    });

```

Lines 94-105 demonstrate how the UI iterates through all available themes, displaying the current selection and emitting `Message::ApplyTheme` on change.

When a user selects a new theme, `handle_apply_theme` (lines 33-39) persists the choice via `Config::save_device_settings`, ensuring the preference survives application restarts. The `string_to_theme` function handles the bidirectional conversion between configuration strings and enum variants.

## Adding Custom Themes to the System

Extending the theme system requires modifications to [`theme.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/theme.rs). To add a new palette like "Solar":

1. Extend the enum with a new variant
2. Define a constant `ColorPalette` with base, normal, and bright colors
3. Return the new palette in the `palette()` method match arm

Because `Theme::ALL` is defined as a static array containing all variants, new themes automatically appear in the Settings UI without additional view code.

## Summary

- **Automatic OS detection** uses the `dark_light` crate cached in a `LazyLock` static to determine system preferences without blocking the UI
- **Four built-in variants** (Auto, Dark, Light, Lupin) provide immediate theming options, with Auto dynamically following OS changes via `OS_COLOR_SCHEME`
- **Iced integration** through the `Base` trait implementation ensures widgets render with correct colors and react to mode changes through `default()` and `mode()` methods
- **Runtime persistence** saves user selections to the device configuration file via `save_device_settings` in [`settings.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/settings.rs)
- **Extensible architecture** allows adding new color palettes by extending the enum and adding constant definitions that automatically populate the UI

## Frequently Asked Questions

### How does the theme system detect if my OS is in dark mode?

The system utilizes the `dark_light` crate to query the operating system's color scheme preference during startup. This value is stored in the `OS_COLOR_SCHEME` static variable using `LazyLock`, ensuring the detection runs exactly once and remains available for the `Auto` theme variant to reference whenever the application needs to determine whether to render in light or dark mode.

### Can I add my own custom color theme to UAD Next Generation?

Yes, the architecture supports custom themes by extending the `Theme` enum in [`crates/uad-gui/src/theme.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/theme.rs). You must define a new variant, create a constant `ColorPalette` with your desired base, normal, and bright colors, and add the corresponding match arm in the `palette()` method. Since the settings UI iterates over `Theme::ALL`, your new theme automatically appears in the radio button list without modifying the view code.

### Where does the application store my selected theme preference?

The selected theme is persisted through the `Config::save_device_settings` method called in `handle_apply_theme` within [`crates/uad-gui/src/views/settings.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/views/settings.rs). This writes the theme string to the application's configuration file, which is loaded on startup and converted back to a `Theme` variant via `string_to_theme` when the GUI initializes.

### What is the "Lupin" theme and how is it different from Light and Dark?

Lupin is a custom color palette hard-coded in [`theme.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/theme.rs) (lines 72-125) alongside the standard Dark and Light themes. While Light and Dark follow traditional high-contrast aesthetics suitable for accessibility and standard OS integration, Lupin provides a distinct visual identity with custom base, normal, and bright color values that offer an alternative to the standard binary choice, demonstrating how the system supports arbitrary custom palettes beyond simple light/dark mode.