cw.MauiExtensions.Services
1.0.1
dotnet add package cw.MauiExtensions.Services --version 1.0.1
NuGet\Install-Package cw.MauiExtensions.Services -Version 1.0.1
<PackageReference Include="cw.MauiExtensions.Services" Version="1.0.1" />
<PackageVersion Include="cw.MauiExtensions.Services" Version="1.0.1" />
<PackageReference Include="cw.MauiExtensions.Services" />
paket add cw.MauiExtensions.Services --version 1.0.1
#r "nuget: cw.MauiExtensions.Services, 1.0.1"
#:package cw.MauiExtensions.Services@1.0.1
#addin nuget:?package=cw.MauiExtensions.Services&version=1.0.1
#tool nuget:?package=cw.MauiExtensions.Services&version=1.0.1
cw.MauiExtensions.Services
A reusable .NET MAUI library providing page presentation services (standalone pages, pages with navigation stack, popups, alert dialogs), ViewModel lifecycle management, and customizable system bar (status bar and navigation bar) handling for Android, Windows.
Features
This library offers:
- ViewPresenter: You can create any type of page: a start page with or without a navigation stack, a page to be pushed on the stack, a page replacing the start page, and modal pages/popups.
- ViewModel Lifecycle Management: Hook your viewmodels to lifecycle events and disposal requests.
- ContentDialog: A overlay-style modal dialog (popup) with semi-transparent backgrounds as the base for your own custom dialogs
- AlertDialog: A simple alert/confirmation dialog with only a title, a message, and up to 2 buttons.
- Smart System Bar Handling: Automatically configure status and navigation bar colors accross all Android API versions.
- Page Removal Events: Subscribe to page removal notifications wherever you want.
The ViewPresenter
The purpose of the ViewPresenter is to be a replacement for AppShell and get rid of some questionable features of AppShell,
in particular:
- URL-based routing which uses web-style navigation where apps are typically state-driven.
- Shell hides too much behavior behind implicit magic: page instantiation, parameter injection, ..
- Dependency Injection is awkward and fragile with Shell.
- Shell enforces UI structure too early.
- Shell doesn't scale well.
You can open and close pages by using the following methods of the ViewPresenter singleton instance:
Method 1: OpenMainPage(Type viewType, object? viewModel)
Creates and returns a new main page instance of the specified type, optionally initialized with the provided
view model. With this method the page is not created in a NavigationPage and so doesn't support the MAUI navigation stack.
- When the method is invoked with a non-null viewModel then the page being instantiated is expected to have a constructor with a viewModel object as parameter. The page then typically binds the viewmodel parameter to its BindingContext.
- When viewModel is null then the page constructor must be parameterless. You can then choose to work with or without a view model in the page code-behind.
Example:
protected override Window CreateWindow(IActivationState? activationState)
{
var page = ViewPresenter.Instance.OpenMainPage(typeof(Views.DemoTabbedPage), null);
return new Window(page);
}
The method is typically invoked at startup of the app in App.CreateWindow which then creates a new Window where
the page will be the "canvas" with the UI.
If the method is called later at a moment where Application.Current.Windows already has a page assigned, then the assigned page
will be replace by the newly created page.
Method 2: OpenMainNavigationPage(Type viewType, object? viewModel)
Creates and returns a new MAUI NavigationPage, or replaces a already assigned one, and assigns the page defined by the specified
view type as root of the navigation stack.
- When the method is invoked with a non-null viewModel then the page being instantiated is expected to have a constructor with a viewModel object as parameter. The page then typically binds the viewmodel parameter to its BindingContext.
- When viewModel is null then the page constructor must be parameterless. You can then choose to work with or without a view model in the page code-behind.
Use this method if you want to create an app that uses a navigation stack for navigating between pages.
Example:
protected override Window CreateWindow(IActivationState? activationState)
{
var page = ViewPresenter.Instance.OpenMainNavigationPage(typeof(Views.HomePage), new ViewModels.HomeViewModel());
return new Window(page);
}
Also here, the method is usually called at startup of the app in App.CreateWindow which then creates a new Window(page). If the method is called later at a moment where Application.Current.Windows already has a page assigned, then the assigned page will be replace by the newly created NavigationPage.
Method 3: PushPageAsync(Type viewType, object? viewModel, int pagesToPopCount = 0)
Opens a new page of the specified type by pushing the page on the MAUI navigation stack. This method requires
a NavigationPage to be already assigned to the current window. See method 2.
- When the method is invoked with a non-null viewModel then the page being instantiated is expected to have a constructor with a viewModel object as parameter. The page then typically binds the viewmodel parameter to its BindingContext.
- When viewModel is null then the page constructor must be parameterless. You can then choose to work with or without a view model in the page code-behind.
Example:
public partial class HomeViewModel : ObservableObject, IPageLifecycleAware
{
[RelayCommand]
async Task OpenNonModalPage()
{
// Navigate to a non-modal page
await ViewPresenter.Instance.PushPageAsync(typeof(NonModalPage), new NonModalViewModel(pageNumber: 1));
}
...
}
Method 4: PopPageAsync(int nrofPagesToPop = 1)
Removes one or more pages from the top of the navigation stack.
Example:
[RelayCommand]
async Task Save()
{
// Inform listeners
WeakReferenceMessenger.Default.Send(new LocationConfiguredMessage(new LocationInfoEvent(_isLocA, StorageLoc)));
await ViewPresenter.Instance.PopPageAsync();
}
Method 5: OpenModalPageAsync(Page modalPage)
Displays the specified page as a modal dialog on top of the current page. The method can be called from any open page and is put on the MAUI modal pages stack which supports multiple modal pages.</br> The modal page can be instructed to open in FullScreen or Overlay mode.</br>
Example - Full screen modal
<?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:vm="clr-namespace:cw.MauiExtensions.Services.Demo.ViewModels"
x:DataType="vm:ModalViewModel"
x:Class="MauiExtensions.Demo.Views.ModalPage"
Title="Modal page"
Background="{AppThemeBinding Light={StaticResource PageBackground}, Dark={StaticResource PageBackgroundDark}}">
<VerticalStackLayout Padding="18,48,18,0"
Spacing="25">
<Button Text="X"
Command="{Binding CloseModalCommand}"/>
<Label Text="This is a modal page. Tap X to close."
FontSize="Medium" />
</VerticalStackLayout>
</ContentPage>
// Full-screen modal page - no need to set mode, FullScreen is default
public partial class ModalPage : ContentPage
{
public ModalPage()
{
InitializeComponent();
// ModalPageMode.FullScreen is the default - no action needed
}
}
[RelayCommand]
async Task OpenModalPage()
{
// Navigate to modal page on the stack
await ViewPresenter.Instance.OpenModalPageAsync(new ModalPage(new ModalViewModel()));
}
Example - Overlay modal / popup
public partial class MyPopupDialog : ContentPage
{
public MyPopupDialog()
{
InitializeComponent();
// Only needed for custom overlay dialogs
ModalPageProperties.SetMode(this, ModalPageMode.Overlay);
}
}
[RelayCommand]
async Task OpenPopupDialog()
{
// Navigate to modal page on the stack
await ViewPresenter.Instance.OpenModalPageAsync(new MyPopupDialog());
}
Remark - See also ContentDialog for popups returning a result.
Method 6: CloseModalPageAsync()
Closes the topmost modal page if one is present on the application's main window. The system will bring back a possible previous modal page or the underlying non-modal page.
Example:
[RelayCommand]
async Task CloseModal()
{
// Close the modal page
await ViewPresenter.Instance.CloseModalPageAsync();
}
AlertDialog
The library comes with a AlertDialog class for simple alert/confirmation scenarios where the popup
only needs a title, a description and 1 or 2 buttons.</br>
The constructor is:
public AlertDialog(string title, string text, string primaryBttnText, string? secondaryBttnText)
- Parameter title is a string that is presented at the top of the popup
- Parameter text defines the text that shows below the title.
- Parameter primaryBttnText defines the text of the 1st button in the dialog. You can assign any meaning to it.
- Parameter secondaryBttnText defines the text of an optional 2nd button in the dialog.
The popup is opened by invoking the ShowAsync() method on the created AlertDialog. The method returns a
ContentDialogResult which is an enum value that can be: None, Primary, or Secondary. None is returned when
the dialog is closed by tapping outside of the popup.
Example:
[RelayCommand]
async Task ShowAlert()
{
var alert = new AlertDialog(title: "Confirm Delete",
text: "Are you sure you want to delete this item? This action cannot be undone.",
primaryBttnText: "Delete",
secondaryBttnText: "Cancel");
var result = await alert.ShowAsync();
if (result == ContentDialogResult.Primary)
{
await DeleteItemAsync();
}
)
You can style 2 things in the AlertDialog:
- The style of the content border. Configure
ResourceKeys.AlertDialogBorderStylefor this. - The style of the 2 buttons. Configure
ResourceKeys.AlertDialogButtonStylefor this.
To make it even easier to popup an alert dialog , the library also provides a static helper method
AlertDialog.ShowAlertAsync(string title, string text, string primaryBttnText, string? secondaryBttnText)</br>
Example:
await Alert.ShowAsync("Error", "You must enter a server address", "OK");
ContentDialog for easy popup dialogs
The library comes with a ContentDialog class that you can use as a base class for your own custom popup dialogs.
It supports: type-safe results, full XAML support, automatic overlay styling, and lifecycle management.
Example:
using cw.MauiExtensions.Services.Views;
var dialog = new ContentDialog
{
ContentView = new MyCustomView()
};
var result = await dialog.ShowAsync();
// Handle result: ContentDialogResult.None, Primary, or Secondary
See the ContentDialog documentation for a detailed description.
System bars and navigation bar coloring
The latest UI recommendations for mobile apps call for a UI where all page background colors extend to the device's status bar, the maui navigation bar (when available) and the system navigation bar (when available).</br> Out of the box MAUI does not provide this functionality on all platforms. Especially on Android support for configuring the system bars across all Android versions is limited.</br>
How does the library help you with this?
1. iOS
On iOS 13+ the color of the status bar is transparant. This has the following advantages:
- On a page with a MAUI navigation stack the MAUI navigation bar is drawn as an overlay on top of the system bar with a vertical offset, so true edge-to-edge display is possible by assigning the same background color to the navigation bar and page background.
- On a page that doesn't use the MAUI navigation stack and on modal pages (full screen or overlay) the background color of
the page extends to the bar. Depending on the value of
IgnoreSafeArea(typically set as a property of the root container of a page) the page content starts below or at the top of the bar. So it is possible to achieve true edge-to-edge display.
Summary - With iOS the library doesn't need to do anything special to achieve edge-to-edge display.
2. Android
To achieve a true edge-to-edge layout on Android the library comes with a number of configuration options. The 2 most
important are: UseSmartSystemBarColoring and UseSmartSystemBarColoringWithModals. Both are true by default. If you
set them to false then the library will not do anything special and the system bars (status bar at the top and navigation
bar at the bottom) will stay as configured by the OS or by other libraries such as CommunityToolkit.Maui.
The next thing to consider is whether you want edge-to-edge for root pages and/or for modal pages.
3.1 Root pages
EdgeToEdgeForRootPages - This option indicates whether at the start of the app (and when the theme changes) the
library must extend the page content under the system bars or not.</br>
- If true (the default for Android API 32+) the library will configure the system not to adjust the view to fit inside the
"safe areas" and not draw a solid color behind the bars on Android running API 32+. This works well for all type of pages, including
a
NavigationPage. Unfortunately it doesn't work as expected when you have aNavigationPageand you want the bars to automatically update when the app is open and you change the theme.</br> Per default there will be a offset for the page content in order to start below the status bar. If you don't want that you must set the optionEdgeToEdgeStartContentBelowBarto false. - If false and when running on Android API < 32, the library will explicitly color the systems bars with the color referenced
by
SystemBarsBackgroundColor(light mode) andSystemBarsBackgroundDarkColor(dark mode). No change is done on the insets and therefore the page starts below the status bar.</br> This is what you must use when your app uses aNavigationPageand also when you want the color of the system bars to be different from the background of the page.
In both cases the library also sets the tint of the icons of the system bars to light or dark by looking at the brightness
of SystemBarsBackgroundColor or SystemBarsBackgroundDarkColor.
Theme changes - Unless UseSmartSystemBarColoring is set to false the library will automatically react to a theme change
and re-draw the system bars.
Required resource settings for any type of page
The library retrieves the color of the system bar from Application.Current.Resources, i.e. from Colors.xaml. The resource keys
can be changed in SystemBarsBackgroundColor and SystemBarsBackgroundDarkColor but if you want to use the default keys you
need a definition for:
- "SystemBarsBackground" and
- "SystemBarsBackgroundDark"
Required resource settings for a MAUI NavigationPage
When the root page is a MAUI NavigationPage and you want to have a edge-to-edge background color then you must also configure the
navigation bar and page background colors to match the system bar colors. This can be easily done by opening the
main page using ViewPresenter.OpenMainNavigationPage. This method automatically sets both the background color and the
text color of the MAUI navigation bar to the color referenced by NavigationBarBackgroundColor or NavigationBarBackgroundDarkColor
and NavigationBarTextColor or NavigationBarTextDarkColor.
If you are OK with with the default expected resource keys then you must configure the following keys in Colors.xaml:
- "SystemBarsBackground" and "SystemBarsBackgroundDark"
- "NavigationBarBackground" and "NavigationBarBackgroundDark"
- "NavigationBarText" and "NavigationBarTextDark"
3.2 Modal and Popup pages
EdgeToEdgeForModalPages - This option indicates whether the library must extend the modal page content under the
system bars or not.</br>
If true (the default) the library will configure the page to extend beneath the system bars on Android running API 32+. This is the best option for a edge-to-edge layout with a single background color on the whole screen.</br> Per default there will be a offset for the page content in order to start below the status bar. If you don't want that you must set the option
EdgeToEdgeStartContentBelowBarto false.If false , or when the API is lower than 32, the library will explicitly color the systems bars with the color referenced by
SystemBarsBackgroundColor(light mode) andSystemBarsBackgroundDarkColor(dark mode). No change is done on the insets and therefore the modal page starts below the status bar.</br> Use this setting also when you want the color of the system bars to be different from the background of the page.
In both cases the tint of the icons of the system bars is determined by the Android system by looking at the brightness of the background color.</br>
Note - Setting EdgeToEdgeForModalPages to true also works well when using CommuniyToolkit.Maui popups across all
Android versions.
Required resource settings for modal pages
You must provide a key definition in Colors.xaml for:
- "SystemBarsBackground" and
- "SystemBarsBackgroundDark"
3. Windows
On Windows all pages start below the title bar and therefore true edge-to-edge display is not possible unless you create a
TitleBar control for the window and style it to match the background color with the page background and, if available,
assign the same color to the MAUI navigation bar.</br>
The TitleBar is created as follows (example in xaml):
<?xml version="1.0" encoding="utf-8" ?>
<TitleBar xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:cw.MauiExtensions.Services.Demo.ViewModels"
Title="Demo App"
Subtitle="Welcome to the Demo App"
Icon="demoicon.png"
HeightRequest="48"
BackgroundColor="{AppThemeBinding Light={StaticResource SystemBarsBackground}, Dark={StaticResource SystemBarsBackgroundDark}}"
ForegroundColor="{AppThemeBinding Light={StaticResource NavigationBarText}, Dark={StaticResource NavigationBarTextDark}}"
x:DataType="vm:DesktopTitleBarViewModel"
x:Class="cw.MauiExtensions.Services.Demo.Views.DesktopTitleBar"
x:Name="RootTitleBar">
<TitleBar.Content>
<Grid ColumnDefinitions="Auto,*,Auto"
Margin="0"
Padding="8,0"
WidthRequest="400">
<Image Grid.Column="0"
Source="info.png"
HeightRequest="16"
WidthRequest="16"
Margin="0,0,8,0"/>
<Grid Grid.Column="1"
Background="Transparent">
<Label Text="Can't drag here"
VerticalOptions="Center"
HorizontalOptions="Start"
FontSize="12"/>
</Grid>
<ImageButton Grid.Column="2"
HeightRequest="24"
WidthRequest="24"
BorderWidth="0"
Source="settings.png"
Background="Transparent"
Command="{Binding BindingContext.ShowSettingsCommand, Source={x:Reference RootTitleBar}}"/>
</Grid>
</TitleBar.Content>
</TitleBar>
And in App.xaml.cs:
protected override Window CreateWindow(IActivationState? activationState)
{
var page = ViewPresenter.Instance.OpenMainNavigationPage(typeof(HomePage), new HomeViewModel());
var titleBar = new DesktopTitleBar(new DesktopTitleBarViewModel());
Window window = new Window()
{
Page = page,
TitleBar = titleBar
};
return window;
}
The TitleBar control is ignored when the app is running on Android or iOS.
Remark - Modal pages (full screen and overlay) also start below the title bar. When a page has a semi-transparant background color then the previous page will shine through and typically will be dimmed. The title bar however will stay untouched.
Summary - With Windows the library doesn't need to do anything special to achieve edge-to-edge display, provided you
configure a custom TitleBar.
ViewModel Lifecycle Management
When using pages and popups, it is important to manage the lifecycle of the associated ViewModels properly. MAUI doesn't provide built-in support for ViewModel lifecycle events, which can lead to memory leaks and unexpected behavior.
cw.MauiExtensions.Services provides automatic ViewModel lifecycle management through two opt-in interfaces:
IPageLifecycleAware
Receive notifications when your ViewModel's page appears or disappears:
using cw.MauiExtensions.Services.Interfaces;
public class MyViewModel : ObservableObject, IPageLifecycleAware
{
public void OnPageCreated()
{
// Called when page appears
// Start timers, refresh data, subscribe to events
}
public void OnPageDestroyed()
{
// Called when page disappears
// Stop timers, pause operations
}
}
Use cases:
- Refresh data when page appears
- Start/stop timers based on visibility
- Track page views for analytics
- Save state when navigating away
IDisposableOnViewClosed
Automatically dispose resources when the page is removed from navigation:
using cw.MauiExtensions.Services.Interfaces;
public class MyViewModel : ObservableObject,
IPageLifecycleAware,
IDisposableOnViewClosed
{
private Timer? _refreshTimer;
private bool _isDisposed;
public void OnPageCreated()
{
_refreshTimer = new Timer(5000);
_refreshTimer.Start();
}
public void OnPageDestroyed()
{
_refreshTimer?.Stop();
}
public void Dispose()
{
if (_isDisposed) return;
_refreshTimer?.Stop();
_refreshTimer?.Dispose();
_refreshTimer = null;
_isDisposed = true;
}
}
Use cases:
- Unsubscribe from event publishers
- Dispose timers and HTTP clients
- Cancel ongoing async operations
- Release unmanaged resources
- Prevent memory leaks
Lifecycle Event Flow
Page Created
↓
ViewPresenter hooks lifecycle events
↓
Page.Appearing → IPageLifecycleAware.OnPageCreated()
↓
[User interacts with page]
↓
Page.Disappearing → IPageLifecycleAware.OnPageDestroyed()
↓
Page Removed/Popped
↓
Unhook events → IDisposableOnViewClosed.Dispose()
↓
PageDestroyed event raised
Page Destroyed Notifications
The library alse provides a PageDestroyed event in ViewPresenter to get a notification when a page is removed. It is triggered in
the following scenarios:
- A page is popped from the navigation stack.
- A modal page is closed.
Note: before the event is raised and when the page has a IDisposableOnPageClosed viewmodel assigned to its BindingContext, the viewmodel's
Dispose method (if implemented) will be called.
WeakReferenceMessenger
Any object in your app can subscribe to the PageDestroyed event to get notified when pages are removed.
If you prefer however a more loosely coupled approach, you can of course also use the CommunityToolkit.Mvvm WeakReferenceMessenger
to broadcast the event to interested objects and/or services.
Do this by subscribing to the PageDestroyed event in a centralized location (e.g. App.xaml.cs) and broadcasting the event via the messenger.</br>
Like so:
using cw.MauiExtensions.Services.Core;
using cw.MauiExtensions.Services.Events;
public partial class App : Application
{
public App()
{
InitializeComponent();
// Subscribe to page removal events
ViewPresenter.Instance.PageDestroyed += OnPageRemoved;
}
private void OnPageRemoved(object? sender, PageRemovedEventArgs e)
{
Debug.WriteLine($"Page removed: {e.RemovedPage.GetType().Name}");
// Optionally broadcast via WeakReferenceMessenger
WeakReferenceMessenger.Default.Send(e);
}
}
An object; e.g. a viewModel can then subscribe to this event like so:
using CommunityToolkit.Mvvm.Messaging;
using cw.MauiExtensions.Services.Events;
public class MyViewModel : ObservableObject,
IDisposableOnViewClosed
{
public MyViewModel()
{
WeakReferenceMessenger.Default.Register<PageRemovedEventArgs>(this, (r, m) =>
{
// Do something
});
}
public void Dispose()
{
WeakReferenceMessenger.Default.UnregisterAll(this);
}
}
Installation
Add a reference to the
cw.MauiExtensions.Servicesproject in your MAUI application.Register the services in your
MauiProgram.cs:
using cw.MauiExtensions.Services.Extensions;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiExtensionsServices() // Add this line
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
}
- Make sure that the following resources are defined:
Required Color Resources
Define these colors in your App.xaml or Colors.xaml (choose your own colors):
<Color x:Key="SystemBarsBackground">#FFFFFF</Color>
<Color x:Key="SystemBarsBackgroundDark">#000000</Color>
<Color x:Key="NavigationBarBackground">#FFFFFF</Color>
<Color x:Key="NavigationBarBackgroundDark">#000000</Color>
<Color x:Key="NavigationBarText">#101010</Color>
<Color x:Key="NavigationBarTextDark">#FFFFFF</Color>
<Color x:Key="ContentDialogBackgroundOverlay">#4C000000</Color>
<Color x:Key="ContentDialogBackgroundOverlayDark">#80000000</Color>
Additional required Colors in the ContentDialogBorder style when AlertDialog is used
<Color x:Key="ContentDialogBorderBackground">#FAFCFE</Color>
<Color x:Key="ContentDialogBorderBackgroundDark">#202020</Color>
<Color x:Key="ContentDialogBorderStroke">#E0E0E0</Color>
<Color x:Key="ContentDialogBorderStrokeDark">#3D3D3D</Color>
Required Styles
Define these styles in your Styles.xaml:
<Style x:Key="ContentDialogBorder" TargetType="Border">
<Setter Property="VerticalOptions" Value="Center" />
<Setter Property="Padding" Value="10,24,10,24"/>
<Setter Property="StrokeShape" Value="RoundRectangle 15"/>
<Setter Property="StrokeThickness" Value="1"/>
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource ContentDialogBorderStroke}, Dark={StaticResource ContentDialogBorderStrokeDark}}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource ContentDialogBorderBackground}, Dark={StaticResource ContentDialogBorderBackgroundDark}}" />
</Style>
<Style x:Key="TextOnlyButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
<Setter Property="BorderWidth" Value="0" />
<Setter Property="Padding" Value="8,4" />
</Style>
Configuration
Configure the library in your MauiProgram.cs to customize resource keys and behavior:
using cw.MauiExtensions.Services.Extensions;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseMauiExtensionsServices(options =>
{
// Customize resource key names if you use different names in your app
options.ResourceKeys.AlertDialogBorderStyle = "ContentDialogBorder";
options.ResourceKeys.AlertDialogButtonStyle = "TextOnlyButton";
options.ResourceKeys.SystemBarsBackgroundColor = "SystemBarsBackground";
options.ResourceKeys.SystemBarsBackgroundDarkColor = "SystemBarsBackgroundDark";
options.ResourceKeys.NavigationBarBackgroundColor = "PageBackground";
options.ResourceKeys.NavigationBarBackgroundDarkColor = "PageBackgroundDark";
options.ResourceKeys.NavigationBarTextColor = "NavigationBarText";
options.ResourceKeys.NavigationBarTextDarkColor = "NavigationBarTextDark";
options.ResourceKeys.ContentDialogBackgroundOverlayColor = "ContentDialogBackgroundOverlay";
options.ResourceKeys.ContentDialogBackgroundOverlayDarkColor = "ContentDialogBackgroundOverlayDark";
options.UseSmartSystemBarColoring = true; // Default: true
options.UseSmartSystemBarColoringWithModals = true; // Default: true
// Configure edge-to-edge display
options.EdgeToEdgeForRootPages = true; // Default: true
options.EdgeToEdgeForModalPages = true; // Default: true
options.EdgeToEdgeStartContentBelowBar = true; // Default: true
})
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
}
Configuration Options
| Property | Type | Default | Description |
|---|---|---|---|
ResourceKeys.AlertDialogBorderStyle |
string |
"ContentDialogBorder" |
Border style applied to alert dialogs |
ResourceKeys.AlertDialogButtonStyle |
string |
"TextOnlyButton" |
Style applied to alert dialog buttons |
ResourceKeys.ContentDialogBackgroundOverlayColor |
string |
"ContentDialogBackgroundOverlay" |
Overlay color for content dialogs (light mode) |
ResourceKeys.ContentDialogBackgroundOverlayDarkColor |
string |
"ContentDialogBackgroundOverlayDark" |
Overlay color for content dialogs (dark mode) |
ResourceKeys.NavigationBarBackgroundColor |
string |
"NavigationBarBackground" |
MAUI NavigationBar background color (light mode) |
ResourceKeys.NavigationBarBackgroundDarkColor |
string |
"NavigationBarBackgroundDark" |
MAUI NavigationBar background color (dark mode) |
ResourceKeys.NavigationBarTextColor |
string |
"NavigationBarText" |
MAUI NavigationBar text color (light mode) |
ResourceKeys.NavigationBarTextDarkColor |
string |
"NavigationBarTextDark" |
MAUI NavigationBar text color (dark mode) |
ResourceKeys.SystemBarsBackgroundColor |
string |
"SystemBarsBackground" |
System bars background color (light mode) |
ResourceKeys.SystemBarsBackgroundDarkColor |
string |
"SystemBarsBackgroundDark" |
System bars background color (dark mode) |
EdgeToEdgeForRootPages |
bool |
true |
Enable edge-to-edge display for root pages |
EdgeToEdgeForModalPages |
bool |
true |
Enable edge-to-edge display for modal pages |
EdgeToEdgeStartContentBelowBar |
bool |
true |
When edge-to-edge is enabled, add padding to start content below the status bar |
UseSmartSystemBarColoring |
bool |
true |
Enable smart system bar coloring at app startup and on theme changes |
UseSmartSystemBarColoringWithModals |
bool |
true |
Enable smart system bar color handling for modal pages and popups |
Troubleshooting
MissingResourceException
If you get a MissingResourceException, ensure you have defined all required color resources in your Colors.xaml or App.xaml.
The exception message will tell you which resource key is missing.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2025 [Jos Huybrighs]
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-android36.0 is compatible. net10.0-ios26.0 is compatible. net10.0-maccatalyst26.0 is compatible. net10.0-windows10.0.19041 is compatible. |
-
net10.0-android36.0
- No dependencies.
-
net10.0-ios26.0
- No dependencies.
-
net10.0-maccatalyst26.0
- No dependencies.
-
net10.0-windows10.0.19041
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.