Shadows in Windows (UWP) XAML Applications – Part 1 – ThemeShadow

I’ve been watching James Montemagno bring his AnimalCrossing app to life.

What’s super impressive is that this doesn’t look anything like a regular out of the box Xamarin.Forms application. The use of custom fonts, curves, rounded corners and shadows gives this app its own unique look. Unfortunately, it would appear that some of the libraries used aren’t supported on Windows (UWP).

This got me thinking about the use of shadows in UWP, which lead to me sinking time into investigating what is and isn’t support. In this series of posts I’ll walk through some of the options.

Built-in Shadows

Before we get into adding our own shadows to elements in our application, it’s worth noting that UWP already comes with a lot of built-in shadow support. The guidance with regards to using shadows in UWP is that “shadows should be used in a purposeful rather than aesthetic manner”. As such, the built-in shadows appear on elements such as popups and tooltips but don’t appear gratuitously on every list item. For example, the following tooltip has a shadow that makes it appear elevated in relation to the rest of the content.

Dark Mode

I also wanted to see what support there was for dark theme. After switching my Windows theme to dark mode it was evident that there isn’t really any support for shadows. For example, here’s the same tooltip in dark mode.

Whilst there is still some illusion of elevation, this predominately comes from the border that’s been added to the tooltip, rather than an actual shadow. There is definitely no visible shadow around the sides and top of the tooltip, like there is in the light theme.

Now you might be saying “well duh, there is a shadow, you just can’t see it on the black background”. To which, my response is that a shadow, when it comes to applications, is much less about a shadow cast by a light hitting and object, and much more about the illusion of elevation and vertical separation of elements (as described by the Microsoft documentation on Z-depth). As such, even in dark mode you’d expect to see some shadow that illustrates a vertical separation of elements.

WinUI 3

Before we move on, I thought I’d take a quick look at whether there are any difference with regards to shadows in WinUI 3 (grab the WinUI 3 templates to get started). The following image shows the exact same button and tooltip combination, this time using the controls from WinUI 3.

Note that other than a change to the shape and border on the tooltip, there’s really no difference to the shadow cast by the tooltip. The behaviour in dark mode it basically the same too. Although I must admit, do like the new look tooltip with the slightly rounded corners and more gradient border, more than the standard UWP look.

Shadows using ThemeShadow

Whilst support for shadows has been around for quite a while for UWP applications, it was only recent that the ThemeShadow class was added (Windows 10, version 1903 – v10.0.18362.0), making it easier for developers to add shadow support to their applications. To demonstrate this in action, we’ll use a rather contrived example of two overlapping Rectangles, as defined in the following XAML.

<Grid>
    <Rectangle x:Name="Rectangle1"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Top"
                HorizontalAlignment="Right" />
    <Rectangle x:Name="Rectangle2"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Left" />
</Grid>

Running this example we can see that both Rectangle shapes appear as one connected shape, flat against the white background. We’ll use a ThemeShadow to elevate the Rectangle2 (bottom left) off the background, and provide some separation from Rectangle1 (top right).

We’ll start by creating a ThemeShadow instance as a Resource with the name SharedShadow. This will be set as the Shadow property on Rectangle2.

<Grid>
    <Grid.Resources>
        <ThemeShadow x:Name="SharedShadow" />
    </Grid.Resources>
    <Grid x:Name="BackgroundGrid" />
    <Rectangle x:Name="Rectangle1"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Top"
                HorizontalAlignment="Right" />
    <Rectangle x:Name="Rectangle2"
                Height="200"
                Width="200"
                Fill="Turquoise"
                VerticalAlignment="Bottom"
                HorizontalAlignment="Left" 
                Shadow="{StaticResource SharedShadow}" />
</Grid>

Note that creating this resource and setting the Shadow property, creates the shadow but it won’t display until you tell the application what elements should show the shadow. This is done by adding elements to the Receivers collection on the ThemeShadow resource.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SharedShadow.Receivers.Add(BackgroundGrid);
}

It’s important to note here that you can’t add an ancestor of the element casting the shadow to the Receivers collection. This seems rather counter-intuitive since you’d quite often want the shadow to appear on the background behind the element casting the shadow (which would typically be the parent grid or panel). However, it’s also easy to solve – in the above XAML a Grid called BackgroundGrid was added on which the shadow can be cast.

Even after doing all this, if you run this example you still won’t see a shadow. This is because all the elements are all technically at the same elevation (i.e. they have the same Z axis value of 0) so there is no shadow to be cast. To fix this, we simply need to translate Rectangle2 in the Z dimension.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SharedShadow.Receivers.Add(BackgroundGrid);	
    Rectangle2.Translation += new Vector3(0, 0, 32);
}

Now we’re getting somewhere. The following image shows a shadow around Rectangle2. However, it still shows the two rectangles as being a single shape.

The reason there is no shadow being shown on Rectangle1 is that we haven’t told the application that it should show the shadow there. As before, the fix for this is to add Rectangle1 to the Receivers list of the ThemeShadow resource.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SharedShadow.Receivers.Add(BackgroundGrid);	
    SharedShadow.Receivers.Add(Rectangle1);
    Rectangle2.Translation += new Vector3(0, 0, 32);
}

And there we have it – a shadow appearing on all sides of Rectangle2.

Of course, we couldn’t just leave it there as I’m sure you’re all wondering what happens in dark mode. Well, there’s good and bad news, as you can see in the following image.

The good news is that the shadow is still being created (see between the rectangles). The bad news is that the shadow hasn’t been inverted with the theme, meaning that you can’t see the shadow against the black background. This is seriously frustrating as it makes the ThemeShadow kind of pointless – it’s main premise was that it was suppose to handle everything for the developer.

Rounded Corners

Updated: I’d forgotten to add this last section on rounded corners in the initial post.

Going back to the designs that James had in the app he’s working on, the items all have rounded corners, giving it a softer, more casual look and feel. To do this in my simple example is relatively easy as I can just set the RadiusX and RadiusY properties.

<Rectangle x:Name="Rectangle2"
            Margin="50"
            Height="200"
            Width="200"
            Fill="Turquoise"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left"
            RadiusX="40"
            RadiusY="40"
            Shadow="{StaticResource SharedShadow}" />

But what happens to my shadow? Well, the answer is nothing good, as you can see from the following image

In addition to not being able to handle dark mode, it also appears that the ThemeShadow can’t handle non-square corners. According to the documentation, there’s also no support color. Overall the ThemeShadow is severely limited in the scenarios that it would be useful for.

Next up we’ll take a more in depth look at the Shadow property in UWP and how you can implement a shadow yourself.

Leave a comment