maui-theming

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

.NET MAUI Theming

.NET MAUI 主题设置

Apply light/dark mode support, custom branded themes, and runtime theme switching in .NET MAUI apps using AppThemeBinding, ResourceDictionary swapping, and system theme detection APIs.
借助AppThemeBinding、ResourceDictionary切换和系统主题检测API,在.NET MAUI应用中实现明暗模式支持、自定义品牌主题以及运行时主题切换。

When to Use

适用场景

  • Adding light and dark mode support to a .NET MAUI app
  • Creating custom branded themes with ResourceDictionary
  • Detecting and responding to system theme changes at runtime
  • Letting users choose a preferred theme (light, dark, or system default)
  • Combining OS-driven theme response with custom color palettes
  • 为.NET MAUI应用添加明暗模式支持
  • 使用ResourceDictionary创建自定义品牌主题
  • 在运行时检测并响应系统主题变化
  • 允许用户选择偏好主题(浅色、深色或系统默认)
  • 将系统驱动的主题响应与自定义调色板相结合

When Not to Use

不适用场景

Inputs

前置条件

  • A .NET MAUI project targeting .NET 8 or later
  • XAML pages or C# UI code that need theme-aware styling
  • 面向.NET 8或更高版本的.NET MAUI项目
  • 需要主题感知样式的XAML页面或C# UI代码

Workflow

操作流程

  1. Detect the current theme approach in the project (AppThemeBinding, ResourceDictionary, or none).
  2. Choose the appropriate strategy: AppThemeBinding for simple light/dark, ResourceDictionary swap for custom/multiple themes, or both combined.
  3. Define theme resources — inline
    AppThemeBinding
    values or separate
    ResourceDictionary
    files with matching keys.
  4. Replace hardcoded colors with
    DynamicResource
    bindings (or
    AppThemeBinding
    markup) throughout XAML pages.
  5. Add system theme detection via
    Application.Current.RequestedTheme
    and the
    RequestedThemeChanged
    event.
  6. Implement user preference persistence with
    Preferences.Set
    /
    Preferences.Get
    and apply on startup.
  7. Verify Android
    ConfigChanges.UiMode
    is set on
    MainActivity
    to avoid activity restarts on theme change.
  8. Test both light and dark themes on at least one target platform, confirming all UI elements respond correctly.
  1. 检测项目中当前的主题实现方式(AppThemeBinding、ResourceDictionary或无)。
  2. 选择合适的策略:简单明暗模式用AppThemeBinding,自定义/多主题用ResourceDictionary切换,或两者结合。
  3. 定义主题资源——内联
    AppThemeBinding
    值或带有匹配键的独立
    ResourceDictionary
    文件。
  4. 在所有XAML页面中用
    DynamicResource
    绑定(或
    AppThemeBinding
    标记)替换硬编码颜色。
  5. 通过
    Application.Current.RequestedTheme
    RequestedThemeChanged
    事件添加系统主题检测。
  6. 使用
    Preferences.Set
    /
    Preferences.Get
    实现用户偏好持久化,并在启动时应用。
  7. 验证Android的
    MainActivity
    中是否设置了
    ConfigChanges.UiMode
    ,避免主题变化时活动重启。
  8. 至少在一个目标平台上测试明暗两种主题,确认所有UI元素都能正确响应。

Choosing an Approach

方案选择

ApproachBest forLimitation
AppThemeBindingAutomatic light/dark with OS — minimal codeOnly two themes (light + dark)
ResourceDictionary swapCustom branded themes, more than two themes, user preferenceMore setup; must use
DynamicResource
everywhere
Both combinedOS-driven response plus custom theme colorsMost flexible but most complex
方案最佳适用场景局限性
AppThemeBinding随系统自动切换明暗模式——代码量极少仅支持两种主题(浅色+深色)
ResourceDictionary切换自定义品牌主题、多主题支持、用户偏好设置设置工作量更大;必须全程使用
DynamicResource
两者结合系统驱动响应+自定义主题颜色灵活性最高但复杂度也最高

AppThemeBinding (OS Light/Dark)

AppThemeBinding(系统明暗模式)

AppThemeBinding
selects a value based on the current system theme. It supports
Light
,
Dark
, and an optional
Default
fallback.
AppThemeBinding
会根据当前系统主题选择对应值。它支持
Light
Dark
以及可选的
Default
回退值。

XAML

XAML示例

xml
<Label Text="Themed text"
       TextColor="{AppThemeBinding Light=Green, Dark=Red}"
       BackgroundColor="{AppThemeBinding Light=White, Dark=Black}" />

<!-- With resource references -->
<Label TextColor="{AppThemeBinding Light={StaticResource LightPrimary},
                                   Dark={StaticResource DarkPrimary}}" />
xml
<Label Text="主题化文本"
       TextColor="{AppThemeBinding Light=Green, Dark=Red}"
       BackgroundColor="{AppThemeBinding Light=White, Dark=Black}" />

<!-- 结合资源引用 -->
<Label TextColor="{AppThemeBinding Light={StaticResource LightPrimary},
                                   Dark={StaticResource DarkPrimary}}" />

C# Extension Methods

C#扩展方法

csharp
var label = new Label();

// Color-specific helper
label.SetAppThemeColor(Label.TextColorProperty, Colors.Green, Colors.Red);

// Generic helper for any bindable property type
label.SetAppTheme<Color>(Label.TextColorProperty, Colors.Green, Colors.Red);
csharp
var label = new Label();

// 颜色专用辅助方法
label.SetAppThemeColor(Label.TextColorProperty, Colors.Green, Colors.Red);

// 适用于任何可绑定属性类型的通用辅助方法
label.SetAppTheme<Color>(Label.TextColorProperty, Colors.Green, Colors.Red);

ResourceDictionary Theming (Custom Themes)

ResourceDictionary主题设置(自定义主题)

Use separate
ResourceDictionary
files with matching keys to define themes, then swap them at runtime.
使用带有匹配键的独立
ResourceDictionary
文件定义主题,然后在运行时切换它们。

Step 1 — Define Theme Dictionaries

步骤1——定义主题字典

When using compiled XAML with
x:Class
(as shown below), each dictionary needs a code-behind that calls
InitializeComponent()
. Dictionaries loaded via
Source
without
x:Class
do not need code-behind.
LightTheme.xaml
xml
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="MyApp.Themes.LightTheme">
    <Color x:Key="PageBackgroundColor">White</Color>
    <Color x:Key="PrimaryTextColor">#333333</Color>
    <Color x:Key="AccentColor">#2196F3</Color>
</ResourceDictionary>
LightTheme.xaml.cs
csharp
namespace MyApp.Themes;

public partial class LightTheme : ResourceDictionary
{
    public LightTheme() => InitializeComponent();
}
Create a matching DarkTheme.xaml / DarkTheme.xaml.cs with the same keys and different values.
当使用带有
x:Class
的编译XAML(如下所示)时,每个字典都需要一个调用
InitializeComponent()
的代码后置文件。通过
Source
加载且不带
x:Class
的字典无需代码后置文件。
LightTheme.xaml
xml
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="MyApp.Themes.LightTheme">
    <Color x:Key="PageBackgroundColor">White</Color>
    <Color x:Key="PrimaryTextColor">#333333</Color>
    <Color x:Key="AccentColor">#2196F3</Color>
</ResourceDictionary>
LightTheme.xaml.cs
csharp
namespace MyApp.Themes;

public partial class LightTheme : ResourceDictionary
{
    public LightTheme() => InitializeComponent();
}
创建对应的DarkTheme.xaml / DarkTheme.xaml.cs,使用相同的键但不同的值。

Step 2 — Consume with DynamicResource

步骤2——通过DynamicResource使用主题

Use
DynamicResource
so values update when the dictionary is swapped at runtime:
xml
<ContentPage BackgroundColor="{DynamicResource PageBackgroundColor}">
    <Label Text="Hello"
           TextColor="{DynamicResource PrimaryTextColor}" />
    <Button Text="Action"
            BackgroundColor="{DynamicResource AccentColor}" />
</ContentPage>
使用
DynamicResource
,这样当字典在运行时切换时值会自动更新:
xml
<ContentPage BackgroundColor="{DynamicResource PageBackgroundColor}">
    <Label Text="Hello"
           TextColor="{DynamicResource PrimaryTextColor}" />
    <Button Text="操作"
            BackgroundColor="{DynamicResource AccentColor}" />
</ContentPage>

Step 3 — Switch Themes at Runtime

步骤3——运行时切换主题

csharp
void ApplyTheme(ResourceDictionary theme)
{
    // Assumes theme dictionaries are the only merged dictionaries.
    // If your App.xaml merges non-theme dictionaries (e.g., converters),
    // move them to Application.Resources directly instead.
    var mergedDictionaries = Application.Current!.Resources.MergedDictionaries;
    mergedDictionaries.Clear();
    mergedDictionaries.Add(theme);
}

// Usage
ApplyTheme(new DarkTheme());
csharp
void ApplyTheme(ResourceDictionary theme)
{
    // 假设主题字典是唯一合并的字典。
    // 如果你的App.xaml合并了非主题字典(例如转换器),请将它们直接移至Application.Resources中。
    var mergedDictionaries = Application.Current!.Resources.MergedDictionaries;
    mergedDictionaries.Clear();
    mergedDictionaries.Add(theme);
}

// 使用示例
ApplyTheme(new DarkTheme());

System Theme Detection

系统主题检测

Read the Current Theme

读取当前主题

csharp
AppTheme currentTheme = Application.Current!.RequestedTheme;
// Returns AppTheme.Light, AppTheme.Dark, or AppTheme.Unspecified
csharp
AppTheme currentTheme = Application.Current!.RequestedTheme;
// 返回AppTheme.Light、AppTheme.Dark或AppTheme.Unspecified

Override the System Theme

覆盖系统主题

csharp
// Force dark mode regardless of OS setting
Application.Current!.UserAppTheme = AppTheme.Dark;

// Reset to follow system theme
Application.Current!.UserAppTheme = AppTheme.Unspecified;
csharp
// 强制深色模式,忽略系统设置
Application.Current!.UserAppTheme = AppTheme.Dark;

// 重置为跟随系统主题
Application.Current!.UserAppTheme = AppTheme.Unspecified;

React to Theme Changes

响应主题变化

csharp
Application.Current!.RequestedThemeChanged += (s, e) =>
{
    AppTheme newTheme = e.RequestedTheme;
    // Update UI or switch ResourceDictionaries
};
csharp
Application.Current!.RequestedThemeChanged += (s, e) =>
{
    AppTheme newTheme = e.RequestedTheme;
    // 更新UI或切换ResourceDictionary
};

Combining Both Approaches

两种方案结合使用

Use
AppThemeBinding
with
DynamicResource
values for maximum flexibility:
xml
<Label TextColor="{AppThemeBinding
    Light={DynamicResource LightPrimary},
    Dark={DynamicResource DarkPrimary}}" />
Or react to system changes and swap full dictionaries:
csharp
Application.Current!.RequestedThemeChanged += (s, e) =>
{
    ApplyTheme(e.RequestedTheme == AppTheme.Dark
        ? new DarkTheme()
        : new LightTheme());
};
AppThemeBinding
DynamicResource
值结合使用以获得最大灵活性:
xml
<Label TextColor="{AppThemeBinding
    Light={DynamicResource LightPrimary},
    Dark={DynamicResource DarkPrimary}}" />
或者响应系统变化并切换完整字典:
csharp
Application.Current!.RequestedThemeChanged += (s, e) =>
{
    ApplyTheme(e.RequestedTheme == AppTheme.Dark
        ? new DarkTheme()
        : new LightTheme());
};

Saving and Restoring User Preference

保存与恢复用户偏好

Store the user's choice with
Preferences
and apply it on startup:
csharp
// Save choice
Preferences.Set("AppTheme", "Dark");

// Restore on startup (in App constructor or CreateWindow)
var saved = Preferences.Get("AppTheme", "System");
Application.Current!.UserAppTheme = saved switch
{
    "Light" => AppTheme.Light,
    "Dark"  => AppTheme.Dark,
    _       => AppTheme.Unspecified
};
使用
Preferences
存储用户选择,并在启动时应用:
csharp
// 保存选择
Preferences.Set("AppTheme", "Dark");

// 启动时恢复(在App构造函数或CreateWindow中)
var saved = Preferences.Get("AppTheme", "System");
Application.Current!.UserAppTheme = saved switch
{
    "Light" => AppTheme.Light,
    "Dark"  => AppTheme.Dark,
    _       => AppTheme.Unspecified
};

Common Pitfalls

常见陷阱

Android: ConfigChanges.UiMode is Required

Android:必须设置ConfigChanges.UiMode

MainActivity
must include
ConfigChanges.UiMode
or theme-change events will not fire and the activity restarts instead of handling the change gracefully:
csharp
[Activity(Theme = "@style/Maui.SplashTheme",
          MainLauncher = true,
          ConfigurationChanges = ConfigChanges.ScreenSize
                               | ConfigChanges.Orientation
                               | ConfigChanges.UiMode  // ← Required for theme detection
                               | ConfigChanges.ScreenLayout
                               | ConfigChanges.SmallestScreenSize
                               | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity { }
Without
UiMode
, toggling dark mode in Android settings causes a full activity restart — losing navigation state and appearing as a crash.
MainActivity
必须包含
ConfigChanges.UiMode
,否则主题变化事件不会触发,活动会重启而非优雅处理变化:
csharp
[Activity(Theme = "@style/Maui.SplashTheme",
          MainLauncher = true,
          ConfigurationChanges = ConfigChanges.ScreenSize
                               | ConfigChanges.Orientation
                               | ConfigChanges.UiMode  // ← 主题检测必需
                               | ConfigChanges.ScreenLayout
                               | ConfigChanges.SmallestScreenSize
                               | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity { }
如果没有
UiMode
,在Android设置中切换深色模式会导致活动完全重启——丢失导航状态,看起来像崩溃。

DynamicResource vs StaticResource

DynamicResource vs StaticResource

When using ResourceDictionary theme switching, you must use
DynamicResource
:
xml
<!-- ✅ Updates when theme dictionary changes -->
<Label TextColor="{DynamicResource PrimaryTextColor}" />

<!-- ❌ Frozen at first load — won't update on theme switch -->
<Label TextColor="{StaticResource PrimaryTextColor}" />
使用ResourceDictionary主题切换时,必须使用
DynamicResource
xml
<!-- ✅ 主题字典变化时会更新 -->
<Label TextColor="{DynamicResource PrimaryTextColor}" />

<!-- ❌ 首次加载后固定不变——主题切换时不会更新 -->
<Label TextColor="{StaticResource PrimaryTextColor}" />

Hardcoded Colors Break Theming

硬编码颜色会破坏主题设置

Avoid inline color values on elements that should respect the theme:
xml
<!-- ❌ Will not change with theme -->
<Label TextColor="#333333" />

<!-- ✅ Theme-aware -->
<Label TextColor="{DynamicResource PrimaryTextColor}" />
避免在应遵循主题的元素上使用内联颜色值:
xml
<!-- ❌ 不会随主题变化 -->
<Label TextColor="#333333" />

<!-- ✅ 支持主题感知 -->
<Label TextColor="{DynamicResource PrimaryTextColor}" />

CSS Themes Cannot Be Swapped at Runtime

CSS主题无法在运行时切换

.NET MAUI supports CSS styling, but CSS-based themes cannot be swapped dynamically. Use ResourceDictionary theming for runtime switching.
.NET MAUI支持CSS样式,但基于CSS的主题无法动态切换。如需运行时切换,请使用ResourceDictionary主题设置。

Theme Keys Must Match Across Dictionaries

主题键必须在所有字典中匹配

Every
x:Key
used in one theme dictionary must exist in all other theme dictionaries. A missing key causes a silent fallback to the default value, leading to inconsistent appearance.
一个主题字典中使用的每个
x:Key
必须存在于所有其他主题字典中。缺失的键会导致静默回退到默认值,导致外观不一致。

Platform Support

平台支持

PlatformMinimum Version
iOS13+
Android10+ (API 29)
macOS Catalyst10.15+
Windows10+
平台最低版本
iOS13+
Android10+(API 29)
macOS Catalyst10.15+
Windows10+

Quick Reference

快速参考

  • OS light/dark
    AppThemeBinding
    markup extension
  • Theme colors in C#
    SetAppThemeColor()
    ,
    SetAppTheme<T>()
  • Read OS theme
    Application.Current.RequestedTheme
  • Force theme
    Application.Current.UserAppTheme = AppTheme.Dark
  • Theme changes
    RequestedThemeChanged
    event
  • Custom switching → Swap
    ResourceDictionary
    in
    MergedDictionaries
  • Runtime bindings
    DynamicResource
    (not
    StaticResource
    )
  • Persist choice
    Preferences.Set
    /
    Preferences.Get
  • 系统明暗模式
    AppThemeBinding
    标记扩展
  • C#中的主题颜色
    SetAppThemeColor()
    SetAppTheme<T>()
  • 读取系统主题
    Application.Current.RequestedTheme
  • 强制主题
    Application.Current.UserAppTheme = AppTheme.Dark
  • 主题变化
    RequestedThemeChanged
    事件
  • 自定义切换 → 在
    MergedDictionaries
    中替换
    ResourceDictionary
  • 运行时绑定
    DynamicResource
    (而非
    StaticResource
  • 持久化选择
    Preferences.Set
    /
    Preferences.Get