Aaron Martin logo
Aaron Martin
Powerapps

DarkMode for your Canvas Apps

DarkMode for your Canvas Apps
0 views
7 min read
#Powerapps

In this post I propose my adaptation in PowerApps of the article by material.io. The ultimate goal will be to have a full colour theme, but before we get there we need to prepare our apps for dark mode.

You can have a look at this post where I explain not only how to add extra themes, but also available in the user settings. Custom Themes

For this post you will see that I have created a "pixel perfect" version in power apps of the example shown at material.io.
Adapting the colours of the bluish PowerApps theme to make it more understandable to what we (and the users) are used to. Before we start let's see what the expected outcome will be:


PowerApps Light and Dark example next to each other

Introduction

Microsoft is probably aiming to implement the Fluent UI2 Design System in power apps. But at the same time it does not want to complicate the work for power users, so it does not seem foreseeable that they will provide a complete set of tools to apply a detailed Theme and support dark mode.

Modern controls have recently appeared, taking a step in the opposite direction to the Pixel Perfect concept of canvas Apps. I just hope that modern controls remain optional and not mandatory. Because

With this post I come to demonstrate that you can have maximum control of the Theme with Darkmode, in a simple way and without dying in the attempt.

You will find the App to play with the dark mode in my Github.

Dark Mode vs Theme

Dark mode should be aligned with the overall theme of the application. Yes, but in due course. Everything at once adds unnecessary complexity.

So I will show my Dark Mode solution in this post and later we will complement it with Custom Themes available from the user settings.

PowerApps Theme Palette Light and Dark Mode

Requirements

Before you download the App from Github remember that I always use a SplashScreen to facilitate data loading and user experience.

You can see in more detail how I implement the SplashScreen and how to manipulate it.

Hands On

In 5 steps we can start to use our DarkMode:

  1. Start Colour Set
  2. Choose DarkMode Colors
  3. DarkMode Switch Function
  4. Use the variant into the App
  5. Switch Mode

Start Colour Set

  • Light Neutrals with 14 colours from White to Black
  • Dark Neutrals with 17 colours from Black to White

Neutral Colors:


ClearCollect (colLightNeutral, 
        {Code: "Neutral-000", Index:0, RGB:ColorValue("#FFFFFF"), Hex: "#FFFFFF"},
        {Code: "Neutral-100", Index:1, RGB:ColorValue("#F7F8F9"), Hex: "#F7F8F9"},
        {Code: "Neutral-200", Index:2, RGB:ColorValue("#F1F2F4"), Hex: "#F1F2F4"},
        {Code: "Neutral-300", Index:3, RGB:ColorValue("#DCDFE4"), Hex: "#DCDFE4"},
        {Code: "Neutral-400", Index:4, RGB:ColorValue("#B3B9C4"), Hex: "#B3B9C4"},
        {Code: "Neutral-500", Index:5, RGB:ColorValue("#8590A2"), Hex: "#8590A2"},
        {Code: "Neutral-600", Index:6, RGB:ColorValue("#758195"), Hex: "#758195"},
        {Code: "Neutral-700", Index:7, RGB:ColorValue("#626F86"), Hex: "#626F86"},
        {Code: "Neutral-800", Index:8, RGB:ColorValue("#44546F"), Hex: "#44546F"},
        {Code: "Neutral-900", Index:9, RGB:ColorValue("#2C3E5D"), Hex: "#2C3E5D"},
        {Code: "Neutral-1000", Index:10, RGB:ColorValue("#172B4D"), Hex: "#172B4D"},
        {Code: "Neutral-1100", Index:11, RGB:ColorValue("#091E42"), Hex: "#091E42"},
        {Code: "Neutral-1200", Index:12, RGB:ColorValue("#040e1e"), Hex: "#040e1e"},
        {Code: "Neutral-1300", Index:13, RGB:ColorValue("#000000"), Hex: "#000000"}
);
ClearCollect (colDarkNeutral, 
        {Code: "DarkNeutral-200", Index:0, RGB:ColorValue("#000000"), Hex: "#000000"},
        {Code: "DarkNeutral-100", Index:1, RGB:ColorValue("#101214"), Hex: "#101214"},
        {Code: "DarkNeutral-000", Index:2, RGB:ColorValue("#161A1D"), Hex: "#161A1D"},
        {Code: "DarkNeutral-100", Index:3, RGB:ColorValue("#1D2125"), Hex: "#1D2125"},
        {Code: "DarkNeutral-200", Index:4, RGB:ColorValue("#22272B"), Hex: "#22272B"},
        {Code: "DarkNeutral-250", Index:5, RGB:ColorValue("#282E33"), Hex: "#2C333A"},
        {Code: "DarkNeutral-300", Index:6, RGB:ColorValue("#2C333A"), Hex: "#2C333A"},
        {Code: "DarkNeutral-350", Index:7, RGB:ColorValue("#38414A"), Hex: "#38414A"},
        {Code: "DarkNeutral-400", Index:8, RGB:ColorValue("#454F59"), Hex: "#454F59"},
        {Code: "DarkNeutral-500", Index:9, RGB:ColorValue("#596773"), Hex: "#596773"},
        {Code: "DarkNeutral-600", Index:10, RGB:ColorValue("#738496"), Hex: "#738496"},
        {Code: "DarkNeutral-700", Index:11, RGB:ColorValue("#8C9BAB"), Hex: "#8C9BAB"},
        {Code: "DarkNeutral-800", Index:12, RGB:ColorValue("#9FADBC"), Hex: "#9FADBC"},
        {Code: "DarkNeutral-900", Index:13, RGB:ColorValue("#B6C2CF"), Hex: "#B6C2CF"},
        {Code: "DarkNeutral-1000", Index:14, RGB:ColorValue("#C7D1DB"), Hex: "#C7D1DB"},
        {Code: "DarkNeutral-1100", Index:15, RGB:ColorValue("#DEE4EA"), Hex: "#DEE4EA"},
        {Code: "DarkNeutral-1200", Index:16, RGB:ColorValue("#FFFFFF"), Hex: "#FFFFFF"}
);

Note that we have both Hexadecimal and RGB. This will be important for our theme to be usable with SVG icons, key for a complete Design System.

Choose DarkMode Colors

We will now select how the diferent labels, background and text color will behave for Light & Dark Mode separately. The starting point and the most important is understanding the following to concepts:

  • Background: There are 2 because you might want sometimes a background lighter/darker depending on our needs.
  • Surceface: This is the panel where we add text and icons. It has to be same or elevated of it's background.
Dark Mode background & surface explained

For our App we haven't used Background2, but just to one side to see how it looks. This would be used when for example, you don't want a White background for your surface to be more elevated.

Set(XColorsX, {
        Mode: {
            //Dark Neutrals goes between 0 Black --> 16 White
            Dark: {
                RGB: { 
                    Background1: LookUp(colDarkNeutral,Index=0).RGB, 
                    Background2: LookUp(colDarkNeutral,Index=1).RGB, 
                    Surface: LookUp(colDarkNeutral,Index=2).RGB,
                    onBackground: LookUp(colDarkNeutral,Index=13).RGB,
                    onSurface: LookUp(colDarkNeutral,Index=14).RGB,
                    Disabled:  LookUp(colDarkNeutral,Index=7).RGB,
                    Medium: LookUp(colDarkNeutral,Index=11).RGB,
                    Emphasys: LookUp(colDarkNeutral,Index=15).RGB,
                    Border1: LookUp(colDarkNeutral,Index=10).RGB
                }
            },
            //Light Neutrals goes between 0 White --> 13 Black
            Light: {
                RGB: { 
                    Background1:LookUp(colLightNeutral,Index=0).RGB,
                    Background2:LookUp(colLightNeutral,Index=1).RGB,
                    Surface: LookUp(colLightNeutral,Index=0).RGB, 
                    onBackground: LookUp(colLightNeutral,Index=11).RGB, 
                    onSurface: LookUp(colLightNeutral,Index=12).RGB,
                    Disabled:  LookUp(colLightNeutral,Index=4).RGB,
                    Medium: LookUp(colLightNeutral,Index=8).RGB,
                    Emphasys: LookUp(colLightNeutral,Index=13).RGB,
                    Border1: LookUp(colLightNeutral,Index=4).RGB

}}}})
  • onBackground: This is the colour used for the text when it is placed on the Background
  • onSurceface: This is the colour used for text when placed on a surface.

The rest of the colours are greyscales depending on the intention of the text.

  • Disabled: Example of text off
  • Medium: Background text that does not attract attention
  • Emphasys: Striking text

It was not necessary to create a border variable, but it is a good example that we can add as many variants as we want.

  • Border1: This is a contrasting grey to highlight the surface.

DarkMode Switch Function

Finally we have to assign the values to the variable that we will use in the app.

This is XMyColorsX and will change every time we switch the Mode (Light/Dark)

Set(XMyColorsX, {
    DarkMode: {
        RGB: { 
                    Background1:  If(varDarkMode,XColorsX.Mode.Dark.RGB.Background1,XColorsX.Mode.Light.RGB.Background1),
                    Background2:  If(varDarkMode,XColorsX.Mode.Dark.RGB.Background2,XColorsX.Mode.Light.RGB.Background2),
                    Surface:      If(varDarkMode,XColorsX.Mode.Dark.RGB.Surface,XColorsX.Mode.Light.RGB.Surface),
                    onBackground: If(varDarkMode,XColorsX.Mode.Dark.RGB.onBackground,XColorsX.Mode.Light.RGB.onBackground),
                    onSurface:    If(varDarkMode,XColorsX.Mode.Dark.RGB.onSurface,XColorsX.Mode.Light.RGB.onSurface),
                    Disabled:     If(varDarkMode,XColorsX.Mode.Dark.RGB.Disabled,XColorsX.Mode.Light.RGB.Disabled),
                    Medium:       If(varDarkMode,XColorsX.Mode.Dark.RGB.Medium,XColorsX.Mode.Light.RGB.Medium),
                    Emphasys:     If(varDarkMode,XColorsX.Mode.Dark.RGB.Emphasys,XColorsX.Mode.Light.RGB.Emphasys),
                    Border1:      If(varDarkMode,XColorsX.Mode.Dark.RGB.Border1,XColorsX.Mode.Light.RGB.Border1)
}}})
  • DarkMode: All your content will change dynamically depending on the value of varDarkMode.
  • RGB: This is the default, but on advanced situations you may want to use Hexadecimal. I'll explain it later.

Use the variant into the App

Now we can use the XMyColorsX variable in our application.

Examples:

  • To fill a background we will use XMyColorsX.DarkMode.RGB.Background1.
  • Writing text on a surface we can use on XMyColorsX.DarkMode.RGB.onSurface.

Switch Mode

For the user to change the DarkMode like in the picture

Switch button Light and Dark Mode

simply change varDarkMode:

`Set(varDarkMode,!varDarkMode)`

And re-run the function that adapts the new colours:

Set(XMyColorsX, {
    DarkMode: {
        RGB: { 
                    Background1:  If(varDarkMode,XColorsX.Mode.Dark.RGB.Background1,XColorsX.Mode.Light.RGB.Background1),
                    Background2:  If(varDarkMode,XColorsX.Mode.Dark.RGB.Background2,XColorsX.Mode.Light.RGB.Background2),
                    Surface:      If(varDarkMode,XColorsX.Mode.Dark.RGB.Surface,XColorsX.Mode.Light.RGB.Surface),
                    onBackground: If(varDarkMode,XColorsX.Mode.Dark.RGB.onBackground,XColorsX.Mode.Light.RGB.onBackground),
                    onSurface:    If(varDarkMode,XColorsX.Mode.Dark.RGB.onSurface,XColorsX.Mode.Light.RGB.onSurface),
                    Disabled:     If(varDarkMode,XColorsX.Mode.Dark.RGB.Disabled,XColorsX.Mode.Light.RGB.Disabled),
                    Medium:       If(varDarkMode,XColorsX.Mode.Dark.RGB.Medium,XColorsX.Mode.Light.RGB.Medium),
                    Emphasys:     If(varDarkMode,XColorsX.Mode.Dark.RGB.Emphasys,XColorsX.Mode.Light.RGB.Emphasys),
                    Border1:      If(varDarkMode,XColorsX.Mode.Dark.RGB.Border1,XColorsX.Mode.Light.RGB.Border1)
}}})

Conclusion

With the help of the post and the application on Github you have enough to maintain a complete darkMode.

But if you want to go further and continue on the way to a full Theme, check out Custom Themes

Next Step

I like to have all the colours in RGB and Hexadecimal at hand for convertibility reasons. In this post I have only shown the RGB because I didn't want to complicate it with something unnecessary. But SVG Icons are a strong reason and I recommend you to use Hexadecimal code, check out my SVG icons post.

No app is perfect without the right icons.