# Boost Your App's User Experience with Responsive Layouts for Portrait and Landscape Modes with .NET MAUI

# Introduction

When developing mobile apps user experience (UX) is key. Today, we will have a look at the role device orientation plays for user experience and how you can take advantage of **.NET MAUI**'s built-in capabilities.

While *Portrait* mode has become the default mode for mobile apps, because we often stare at the screen while holding the device in one hand, it may be a good idea to offer a *Landscape* version of your User Interface (UI) as well. *Landscape* mode greatly increases the user experience of an app, because the screen width is just too small in *Portrait* mode when dealing with images, videos and other types of horizontally organized graphics and data. *Landscape* mode enables your users to view such information and data in a more user-friendly and orientation optimized way.

In this blog article, I will how you how to respond to the device orientation in your .NET MAUI app using [state triggers](https://learn.microsoft.com/dotnet/maui/fundamentals/triggers#state-triggers) and update the layout **entirely in XAML markup** - no C# code required. As always, you can find the full code for this post in my [**sample repository**](https://github.com/ewerspej/maui-samples).

> **Note:** The concepts in this article are largely applicable to Xamarin.Forms as well.

# Portrait and Landscape

Let's have a look at the sample app, which I have extended with a page that displays a video (*using the* [*MediaElement*](https://learn.microsoft.com/dotnet/communitytoolkit/maui/views/mediaelement) *from the Community Toolkit)* and two custom buttons, one to play and another one to pause the video.

**Portrait Mode:**

![App in Landscape Mode with Video view, Play and Pause buttons below Video](https://cdn.hashnode.com/res/hashnode/image/upload/v1677745814933/bf245baf-3198-49ee-9519-26534bc8b2e3.png align="center")

When the device is in *Portrait* mode, the page looks fine, the video takes up the entire width of the screen and the buttons are located below the video, as expected.

**Landscape Mode:**

![App in Landscape mode with small Video view, Play and Pause buttons below Video](https://cdn.hashnode.com/res/hashnode/image/upload/v1677746240811/ebe20001-4bfe-4874-8e88-bea9c0a56f19.png align="center")

In *Landscape* mode, however, the video is extremely small and the buttons are still stacked horizontally below the video, which doesn't look great and provides a poor user experience.

This happens, because the same Layout is used for both *Portrait* and *Landscape*. Instead, let's move the video to the left side of the screen and stack the buttons vertically on the right side when the device is in Landscape mode to look like this:

![App in appropriate Landscape mode with large Video view on the left and stacked Button panel on the right](https://cdn.hashnode.com/res/hashnode/image/upload/v1677746367428/1a54f65b-247e-4da8-b047-82429426f69f.png align="center")

First, let's have a look at the layout of the page and then make a few changes to it to achieve the desired result.

# Page Layout

I have used a `Grid` to place the video at the top and the buttons at the bottom of the page, where the top row takes up one third of the available space and the bottom row takes up two thirds of the available space by defining them as follows: `RowDefinitions="*,2*"`.

The `MediaElement`, which acts as the video player, is placed into the first row, first column of the `Grid` by setting the attached properties `Grid.Row="0"` and `Grid.Column="0"`. This will be useful later on.

The buttons are located inside of a `StackLayout` which has its `Orientation` property set to `"Horizontal"`, which means that they will be placed next to each other horizontally. The `StackLayout` itself is placed in the second row, first column of the `Grid`.

```xml
<?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:views="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:Class="MauiSamples.Views.VideoPage"
             Shell.NavBarIsVisible="False">

  <Grid
    RowDefinitions="*,2*"
    ColumnDefinitions="*">

    <views:MediaElement
      Grid.Row="0"
      Grid.Column="0"
      x:Name="VideoPlayer"
      HorizontalOptions="Fill"
      Source="https://github.com/ewerspej/maui-samples/blob/main/assets/frog113403.mp4?raw=true"
      ShouldShowPlaybackControls="False" />

    <StackLayout
      Grid.Row="1"
      Grid.Column="0"
      VerticalOptions="Center"
      HorizontalOptions="Center"
      Spacing="20"
      Orientation="Horizontal">
      <Button
        HorizontalOptions="Center"
        Text="Play"
        Pressed="OnPlayPressed" />
      <Button
        HorizontalOptions="Center"
        Text="Pause"
        Pressed="OnPausePressed" />
    </StackLayout>

  </Grid>
</ContentPage>
```

This layout currently only looks great in *Portrait* mode, but not in *Landscape* as we've seen above. Let's fix it so that it looks prettier and more user friendly.

The best part is, we don't need to write a single line of C# code for this and we also don't need completely separate layouts, either. Instead, we can simply modify the existing layout based on the device orientation using styles and triggers. Let me show you how to do that next.

# Hello, *OrientationStateTrigger*!

Time to introduce you to a type of trigger called [OrientationStateTrigger](https://learn.microsoft.com/dotnet/maui/fundamentals/triggers#orientation-state-trigger). This trigger comes built-in with .NET MAUI *(and Xamarin.Forms)* and allows you to use [Visual States](https://learn.microsoft.com/dotnet/maui/user-interface/visual-states) and [Styles](https://learn.microsoft.com/dotnet/maui/user-interface/styles/xaml) to modify the visual appearance as well as the layout properties of a View.

## Defining Styles and Triggers

For each [Visual Element](https://learn.microsoft.com/dotnet/api/microsoft.maui.controls.visualelement) that we need to update based on the device orientation, we can add a Style inside of a `ResourceDictionary` which we add to our `VideoPage`:

```xml
<ContentPage.Resources>
  <ResourceDictionary>
    <Style TargetType="Grid" x:Key="VideoGridStyle">
    </Style>
    <Style TargetType="StackLayout" x:Key="ButtonStackStyle">
    </Style>
  </ResourceDictionary>
</ContentPage.Resources>
```

Inside of these styles, we can define the different [Visual States](https://learn.microsoft.com/dotnet/maui/user-interface/visual-states) that we want to use and attach an `OrientationStateTrigger` to each. These triggers are then used to apply the associated styles.

Each style receives a setter that defines the different visual states, in our case those visual states will be `"Portrait"` and `"Landscape"`. Then, inside of each visual state, we can add the appropriate `OrientationStateTrigger` for the applicable device orientation:

```xml
<ContentPage.Resources>
  <ResourceDictionary>
    <Style TargetType="Grid" x:Key="VideoGridStyle">
      <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
          <VisualStateGroup>
            <VisualState x:Name="Portrait">
              <VisualState.StateTriggers>
                <OrientationStateTrigger Orientation="Portrait" />
              </VisualState.StateTriggers>
            </VisualState>
            <VisualState x:Name="Landscape">
              <VisualState.StateTriggers>
                <OrientationStateTrigger Orientation="Landscape" />
              </VisualState.StateTriggers>
            </VisualState>
          </VisualStateGroup>
        </VisualStateGroupList>
      </Setter>
    </Style>
    <!-- ... -->
  </ResourceDictionary>
</ContentPage.Resources>
```

Once the triggers are setup, the setters of the visual states for our *Layout* elements can be added. We'll do this for the `Grid` and the `StackLayout` styles, one after the other, next.

## Visual State Setters for the Grid

For our `Grid`, we need to change the `RowDefinitions` and `ColumnDefinitions` based on the device orientation. Instead of having two rows and a single column, we now need a single row and two columns, which we can achieve by adding the following setters:

```xml
<ContentPage.Resources>
  <ResourceDictionary>
    <Style TargetType="Grid" x:Key="VideoGridStyle">
      <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
          <VisualStateGroup>
            <VisualState x:Name="Portrait">
              <VisualState.StateTriggers>
                <OrientationStateTrigger Orientation="Portrait" />
              </VisualState.StateTriggers>
              <VisualState.Setters>
                <Setter Property="RowDefinitions" Value="*,2*" />
                <Setter Property="ColumnDefinitions" Value="*" />
              </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="Landscape">
              <VisualState.StateTriggers>
                <OrientationStateTrigger Orientation="Landscape" />
              </VisualState.StateTriggers>
              <VisualState.Setters>
                <Setter Property="RowDefinitions" Value="*" />
                <Setter Property="ColumnDefinitions" Value="2*,*" />
              </VisualState.Setters>
            </VisualState>
          </VisualStateGroup>
        </VisualStateGroupList>
      </Setter>
    </Style>
    <!-- ... -->
  </ResourceDictionary>
</ContentPage.Resources>
```

## Visual State Setters for the StackLayout

The `StackLayout` on the other hand needs to change not only its orientation, but also its location *inside* of the `Grid` based on the device orientation.

For *Portrait* mode, the `Orientation` property must be set to `"Horizontal"` while it needs to be set to `"Vertical"` in *Landscape* mode. In *Portrait* mode, the `StackLayout` will be in the second row, first column of the `Grid`, while it needs to be in the first row, second column of the `Grid` in *Landscape* mode. We can achieve this by defining the following setters for the `StackLayout`:

```xml
<ContentPage.Resources>
  <ResourceDictionary>
    <!-- ... -->
    <Style TargetType="StackLayout" x:Key="ButtonStackStyle">
      <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
          <VisualStateGroup>
            <VisualState x:Name="Portrait">
              <VisualState.StateTriggers>
                <OrientationStateTrigger Orientation="Portrait" />
              </VisualState.StateTriggers>
              <VisualState.Setters>
                <Setter Property="Orientation" Value="Horizontal" />
                <Setter Property="Grid.Row" Value="1" />
                <Setter Property="Grid.Column" Value="0" />
              </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="Landscape">
              <VisualState.StateTriggers>
                <OrientationStateTrigger Orientation="Landscape" />
              </VisualState.StateTriggers>
              <VisualState.Setters>
                <Setter Property="Orientation" Value="Vertical" />
                <Setter Property="Grid.Row" Value="0" />
                <Setter Property="Grid.Column" Value="1" />
              </VisualState.Setters>
            </VisualState>
          </VisualStateGroup>
        </VisualStateGroupList>
      </Setter>
    </Style>
  </ResourceDictionary>
</ContentPage.Resources>
```

## Applying the Styles

Last, but not least, we need to apply the styles that we just defined to the `Grid` and the `StackLayout` in our page. Note that the styles are named, which means that they must be explicitly assigned to a visual element in order to take effect:

```xml
<Grid
  Style="{StaticResource VideoGridStyle}">

  <views:MediaElement
    Grid.Row="0"
    Grid.Column="0"
    Margin="0"
    x:Name="VideoPlayer"
    HorizontalOptions="Fill"
    Source="https://github.com/ewerspej/maui-samples/blob/main/assets/frog113403.mp4?raw=true"
    ShouldShowPlaybackControls="False" />

  <StackLayout
    VerticalOptions="Center"
    HorizontalOptions="Center"
    Spacing="20"
    Style="{StaticResource ButtonStackStyle}">
    <Button
      HorizontalOptions="Center"
      Text="Play"
      Pressed="OnPlayPressed" />
    <Button
      HorizontalOptions="Center"
      Text="Pause"
      Pressed="OnPausePressed" />
  </StackLayout>

</Grid>
```

> **Important:** In order to apply the styles correctly, we need to make some modifications to the existing views in our page:
> 
> The `RowDefinitions` and `ColumnDefinitions` assignments of the `Grid` will be removed, just like the `Orientation` as well as the `Grid.Row` and `Grid.Column` settings of the `StackLayout`. This is necessary, because property setters that are applied directly on a `VisualElement` always take precedence over any applied styles, which would prevent the OrientationStateTriggers to update these properties using the defined styles.

The `MediaElement` itself does **not** receive any styles or additional property setters, because it already is set up correctly for both device orientations, which is why it keeps its original settings for `Grid.Row` and `Grid.Column` *(remember when I wrote further up that setting both row and column properties will be useful later on - this is later on)*. It will always remain in the first row, first column - independent of the device orientation. This way, we can keep things simple.

# Result

Now, when we run the app again and rotate the device into *Landscape* mode, the video will be located on the left side taking up much of the available vertical space, while the buttons are now stacked vertically on the right:

![App in appropriate Landscape mode with large Video view on the left and stacked Button panel on the right](https://cdn.hashnode.com/res/hashnode/image/upload/v1677746391384/c11e20aa-0d51-4c93-a190-a43f945d9d0f.png align="center")

🏆 **Awesome**, this looks so much better! `OrientationStateTrigger` FTW! 💪

# Conclusion and next steps

Using state triggers and styles, we can easily add responsive layouts to our app based on the device orientation and thus offer a rich user experience for both Portrait and Landscape modes - entirely in XAML, no C# code required.

Combining this approach with some other tricks as well as some platform-specific code, we can even build immersive user experiences, such as full-screen mode, which requires some additional work and will be covered in a separate article.

If you enjoyed this blog post, then follow me on [**LinkedIn**](https://www.linkedin.com/in/jewerspeters), subscribe to this [**blog**](https://hashnode.com/@ewerspej) and star the [**GitHub** **repository**](https://github.com/ewerspej/maui-samples) for this post so you don't miss out on any future posts and developments.
