Skip to content
Home » Basics of .NET MAUI – Part 14 – Themes

Basics of .NET MAUI – Part 14 – Themes

Spread the love

Mobile devices usually include light and dark themes which can be set manually by the user or change automatically when some conditions are met. For example, your cell phone will probably switch to dark mode when it gets dark in the evening. This way the screen will be easier on the eyes.

If you want to implement this functionality in your app, you have to set the properties that are supposed to depend on the current theme to different values, so one value for the light theme and another for the dark theme. To do that, we use the AppThemeBinding markup extension. Let’s first demonstrate it using the TestPage.

The source code for this article is available on Github.

Light Theme, Dark Theme

Delete the code from the TestPage.xaml file and implement the page like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Slugrace"
             x:Class="Slugrace.Views.TestPage"
             Title="TestPage"
             Padding="20"
             BackgroundColor="DarkGoldenrod">
    <FlexLayout
        Direction="Column"
        JustifyContent="SpaceEvenly"
        AlignItems="Center"
        BackgroundColor="White">
        <Label
            Text="Hey, what a beautiful day!"
            FontSize="26" />
        <Image 
            Source="Speedster.png" 
            Scale=".9"/>
        <Button
            Text="Press"
            BackgroundColor="Black"
            TextColor="White" />
    </FlexLayout>
</ContentPage>

Now run the app on Android:

Nothing special, just a label, an image and a button inside a FlexLayout with a white background color and the background color of the ContentPage set to a shade of golden.

Let’s modify the code and deliver distinct values for some of the properties. For example the FlexLayout background color should be white in light theme and black in dark theme. The button should also change from black to white, and the text on it the other way around.

We’ll also change the label text. First, let’s temporarily define two static properties in the Helpers class:

namespace Slugrace
{
    public static class Helpers
    {
        public static string LightThemeText { get; set; } = "Hey, what a beautiful day!";
        public static string DarkThemeText { get; set; } = "Hey, what a beautiful night!";

        public static void ValidateNumericInputAndSetState(...)
        ...

We’re going to use them in the TestPage. Here’s the modified code:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    <FlexLayout
        ...
        BackgroundColor="{AppThemeBinding Light=White, Dark=Black}">
        <Label
            Text="{AppThemeBinding 
                Light={x:Static local:Helpers.LightThemeText},
                Dark={x:Static local:Helpers.DarkThemeText}}"            
            FontSize="26" />
        ...
        <Button
            Text="Press"
            BackgroundColor="{AppThemeBinding Light=Black, Dark=White}"
            TextColor="{AppThemeBinding Light=White, Dark=Black}" />
    </FlexLayout>
</ContentPage>

To see the themes in action, we have to pick them manually in the emulator. How to exactly do that depends on the emulator, but you will probably have to go to the settings and find it there under Display or something like that. In my emulator it looks like so:

Switch to dark theme. Now our app uses the set of values defined for the dark theme:

You typically change colors in the dark theme to make the text more readable, but you can change any properties you like. Let’s now implement themes in the Slugrace app. But first, remove the two static properties you just added in the Helpers class and set the label text in the TestPage manually to what it was originally.

Themes in Our Application

We’re going to apply themes in our app globally and locally. Let’s start with the former. Here’s the App.xaml file with the themes implemented:

<?xml version = "1.0" encoding = "UTF-8" ?>
<Application ...>
    ...
            <!--Colors-->
            ...
            <Color x:Key="normalEntryColor">White</Color>            
            <Color x:Key="lightPrimaryColor">#FFFFCC</Color>            
            ...                        
            <!--Other values-->
            <FontAttributes x:Key="emphasized">Bold</FontAttributes>

            <Style TargetType="ContentPage"
                   ApplyToDerivedTypes="True">
                <Setter Property="BackgroundColor" 
                        Value="{AppThemeBinding Light={StaticResource lightPrimaryColor}, Dark=Black}" />
            </Style>

            <Style TargetType="Border">
                <Setter Property="Stroke" 
                        Value="{AppThemeBinding 
                            Light={StaticResource mainButtonColor}, 
                            Dark={StaticResource lightPrimaryColor}}"/>
                <Setter Property="StrokeShape" Value="RoundRectangle 10, 10, 10, 10" />                
                ...
            <Style TargetType="Button">
                <Setter Property="BackgroundColor" 
                        Value="{AppThemeBinding 
                            Light={StaticResource mainButtonColor},
                            Dark={StaticResource buttonTextColor}}"/>
                <Setter Property="TextColor" 
                        Value="{AppThemeBinding 
                            Light={StaticResource buttonTextColor},
                            Dark={StaticResource mainButtonColor}}" />
                <Setter Property="FontSize" Value="{StaticResource defaultFontSize}" />                              
                ...
                            <VisualState x:Name="PointerOver">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" 
                                            Value="{AppThemeBinding 
                                                Light={StaticResource pressedButtonColor},
                                                Dark={StaticResource lightPrimaryColor}}" />
                                </VisualState.Setters>
                            </VisualState>

                            <VisualState x:Name="Pressed">
                                <VisualState.Setters>
                                    <Setter Property="Scale" Value=".98" />                                    
                                    <Setter Property="BackgroundColor" 
                                            Value="{AppThemeBinding 
                                                Light={StaticResource pressedButtonColor},
                                                Dark={StaticResource lightPrimaryColor}}" />
                                </VisualState.Setters>
                            ...

As you can see, I extracted the background color into a resource and used it to set the background color of ContentPage in the light theme. In the dark theme the background color is set to black.

The colors of the BackgroundColor and TextColor properties of Button are switched in the two themes.

I also used the new lightPrimaryColor resource to set the Stroke property of Border and the background color of Button in the PointerOver and Pressed visual states.

And now let’s have a look at some local changes.

In the RacePage a different image is used in dark theme for the Sound button. Otherwise it would be hardly visible in this theme. You can grab the sound_on_dark.png image and the other one, sound_off_dark.png (which we’re going to need later) from my Github repository. Paste them to the Resources/Images folder. Here’s how this is implemented in the RacePage.xaml file:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage ...>
    ...
        <!--the buttons-->        
        <VerticalStackLayout
            ...
            <Button
                Text="Instructions" />
            <Button 
                ImageSource="{AppThemeBinding Light=sound_on.png, Dark=sound_on_dark.png}" 
                WidthRequest="{OnPlatform 125, Android=150}"
                ...

In the Bets content view I modified the colors of the slider:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView ...>

    <ContentView.Resources>
        <Style TargetType="Label" BasedOn="{StaticResource labelBaseStyle}" />
        <Style TargetType="Slider">
            <Setter Property="ThumbColor" 
                    Value="{AppThemeBinding 
                        Light={StaticResource mainButtonColor},
                        Dark={StaticResource buttonTextColor}}" />
            <Setter Property="MinimumTrackColor" 
                    Value="{AppThemeBinding 
                        Light={StaticResource mainButtonColor},
                        Dark={StaticResource buttonTextColor}}" />
        </Style>
    ...

I also defined new color resources in the PlayerBet and PlayerResult content views. They’re used to set the background color of the Grid and they look the same:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView ...>

    <ContentView.Resources>
        <Color x:Key="backgroundColorLight">#FFF4E5</Color>
        <Color x:Key="backgroundColorDark">Black</Color>
        <Style TargetType="Label"
                   ...
    <Grid 
        BackgroundColor="{OnPlatform 
            Android={AppThemeBinding 
                Light={StaticResource backgroundColorLight},
                Dark={StaticResource backgroundColorDark}}}"
        ...

And that’s it. Let’s now compare all the pages in light theme and dark theme: Here’s the SettingsPage:

Light theme

Dark theme

Here’s the RacePage with the Bets panel:

Light theme

Dark theme

Here’s the RacePage with the Results panel:

Light theme

Dark theme

Here’s the GameOverPage:

Light theme

Dark theme

If you’re wondering why the labels are white in the dark theme, even if we didn’t define their color for that theme, this is because default values for either theme are defined in the Resources/Styles/Style.xaml file. Our application is now beautifully styled, it supports the light and dark themes on Android and the only thing it lacks is data. In the next part of the series we’ll be briefly talking about namespaces in XAML and then, in the following parts, we’ll see how to provide data and bind to it.


Spread the love

Leave a Reply