Theme an app

.NET Multi-platform App UI (.NET MAUI) apps can respond to style changes dynamically at runtime by using the DynamicResource markup extension. This markup extension is similar to the StaticResource markup extension, in that both use a dictionary key to fetch a value from a ResourceDictionary. However, while the StaticResource markup extension performs a single dictionary lookup, the DynamicResource markup extension maintains a link to the dictionary key. Therefore, if the value associated with the key is replaced, the change is applied to the VisualElement. This enables runtime theming to be implemented in .NET MAUI apps.

Important

.NET Multi-platform App UI (.NET MAUI) is currently in preview. This content relates to a pre-release product that may be substantially modified before it's released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

The process for implementing runtime theming in a .NET MAUI app is as follows:

  1. Define the resources for each theme in a ResourceDictionary. For more information, see Define themes.
  2. Set a default theme in the app's App.xaml file. For more information, see Set a default theme.
  3. Consume theme resources in the app, using the DynamicResource markup extension. For more information, see Consume theme resources.
  4. Add code to load a theme at runtime. For more information, see Load a theme at runtime.

Important

Use the StaticResource markup extension if you don't need to change the app theme at runtime.

The following screenshot shows themed pages, with the iOS app using a light theme and the Android app using a dark theme:

Screenshot of the main page of a themed app.

Note

Changing a theme at runtime requires the use of XAML styles, and is not possible using CSS.

Define themes

A theme is defined as a collection of resource objects stored in a ResourceDictionary.

The following example shows a ResourceDictionary for a light theme named LightTheme:

<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="ThemingDemo.LightTheme">
    <Color x:Key="PageBackgroundColor">White</Color>
    <Color x:Key="NavigationBarColor">WhiteSmoke</Color>
    <Color x:Key="PrimaryColor">WhiteSmoke</Color>
    <Color x:Key="SecondaryColor">Black</Color>
    <Color x:Key="PrimaryTextColor">Black</Color>
    <Color x:Key="SecondaryTextColor">White</Color>
    <Color x:Key="TertiaryTextColor">Gray</Color>
    <Color x:Key="TransparentColor">Transparent</Color>
</ResourceDictionary>

The following example shows a ResourceDictionary for a dark theme named DarkTheme:

<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="ThemingDemo.DarkTheme">
    <Color x:Key="PageBackgroundColor">Black</Color>
    <Color x:Key="NavigationBarColor">Teal</Color>
    <Color x:Key="PrimaryColor">Teal</Color>
    <Color x:Key="SecondaryColor">White</Color>
    <Color x:Key="PrimaryTextColor">White</Color>
    <Color x:Key="SecondaryTextColor">White</Color>
    <Color x:Key="TertiaryTextColor">WhiteSmoke</Color>
    <Color x:Key="TransparentColor">Transparent</Color>
</ResourceDictionary>

Each ResourceDictionary contains Color resources that define their respective themes, with each ResourceDictionary using identical key values. For more information about resource dictionaries, see Resource Dictionaries.

Important

A code behind file is required for each ResourceDictionary, which calls the InitializeComponent method. This is necessary so that a CLR object representing the chosen theme can be created at runtime.

Set a default theme

An app requires a default theme, so that controls have values for the resources they consume. A default theme can be set by merging the theme's ResourceDictionary into the app-level ResourceDictionary that's defined in App.xaml:

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ThemingDemo.App">
    <Application.Resources>
        <ResourceDictionary Source="Themes/LightTheme.xaml" />
    </Application.Resources>
</Application>

For more information about merging resource dictionaries, see Merged resource dictionaries.

Consume theme resources

When an app wants to consume a resource that's stored in a ResourceDictionary that represents a theme, it should do so with the DynamicResource markup extension. This ensures that if a different theme is selected at runtime, the values from the new theme will be applied.

The following example shows three styles from that can be applied to all Label objects in app:

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ThemingDemo.App">
    <Application.Resources>

        <Style x:Key="LargeLabelStyle"
               TargetType="Label">
            <Setter Property="TextColor"
                    Value="{DynamicResource SecondaryTextColor}" />
            <Setter Property="FontSize"
                    Value="30" />
        </Style>

        <Style x:Key="MediumLabelStyle"
               TargetType="Label">
            <Setter Property="TextColor"
                    Value="{DynamicResource PrimaryTextColor}" />
            <Setter Property="FontSize"
                    Value="25" />
        </Style>

        <Style x:Key="SmallLabelStyle"
               TargetType="Label">
            <Setter Property="TextColor"
                    Value="{DynamicResource TertiaryTextColor}" />
            <Setter Property="FontSize"
                    Value="15" />
        </Style>

    </Application.Resources>
</Application>

These styles are defined in the app-level resource dictionary, so that they can be consumed by multiple pages. Each style consumes theme resources with the DynamicResource markup extension.

These styles are then consumed by pages:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ThemingDemo"
             x:Class="ThemingDemo.UserSummaryPage"
             Title="User Summary"
             BackgroundColor="{DynamicResource PageBackgroundColor}">
    ...
    <ScrollView>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="200" />
                <RowDefinition Height="120" />
                <RowDefinition Height="70" />
            </Grid.RowDefinitions>
            <Grid BackgroundColor="{DynamicResource PrimaryColor}">
                <Label Text="Face-Palm Monkey"
                       VerticalOptions="Center"
                       Margin="15"
                       Style="{StaticResource MediumLabelStyle}" />
                ...
            </Grid>
            <StackLayout Grid.Row="1"
                         Margin="10">
                <Label Text="This monkey reacts appropriately to ridiculous assertions and actions."
                       Style="{StaticResource SmallLabelStyle}" />
                <Label Text="  &#x2022; Cynical but not unfriendly."
                       Style="{StaticResource SmallLabelStyle}" />
                <Label Text="  &#x2022; Seven varieties of grimaces."
                       Style="{StaticResource SmallLabelStyle}" />
                <Label Text="  &#x2022; Doesn't laugh at your jokes."
                       Style="{StaticResource SmallLabelStyle}" />
            </StackLayout>
            ...
        </Grid>
    </ScrollView>
</ContentPage>

When a theme resource is consumed directly, it should be consumed with the DynamicResource markup extension. However, when a style that uses the DynamicResource markup extension is consumed, it should be consumed with the StaticResource markup extension.

Load a theme at runtime

When a theme is selected at runtime, an app should:

  1. Remove the current theme from the app. This is achieved by clearing the MergedDictionaries property of the app-level ResourceDictionary.
  2. Load the selected theme. This is achieved by adding an instance of the selected theme to the MergedDictionaries property of the app-level ResourceDictionary.

Any VisualElement objects that set properties with the DynamicResource markup extension will then apply the new theme values. This occurs because the DynamicResource markup extension maintains a link to dictionary keys. Therefore, when the values associated with keys are replaced, the changes are applied to the VisualElement objects.

In the sample application, a theme is selected via a modal page that contains a Picker. The following code shows the OnPickerSelectionChanged method, which is executed when the selected theme changes:

The following example shows removing the current theme and loading a new theme:

ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;
if (mergedDictionaries != null)
{
    mergedDictionaries.Clear();
    mergedDictionaries.Add(new DarkTheme());
}