Shiny.Maui.Shell 4.2.0

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Shiny.Maui.Shell --version 4.2.0
                    
NuGet\Install-Package Shiny.Maui.Shell -Version 4.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Shiny.Maui.Shell" Version="4.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Shiny.Maui.Shell" Version="4.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Shiny.Maui.Shell" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Shiny.Maui.Shell --version 4.2.0
                    
#r "nuget: Shiny.Maui.Shell, 4.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Shiny.Maui.Shell@4.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Shiny.Maui.Shell&version=4.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Shiny.Maui.Shell&version=4.2.0
                    
Install as a Cake Tool

Shiny MAUI Shell

NuGet

Make .NET MAUI Shell shinier with ViewModel lifecycle management, navigation services, and source generation to remove boilerplate, reduce errors, and make your app testable.

Inspired by Prism Library by Dan Siegel and Brian Lagunas.

Full Documentation


Features

🧭 Navigation β€” INavigator

Capability Description
Route-based NavigateTo("Detail", args: [("Id", "123")])
ViewModel-based NavigateTo<DetailViewModel>(vm => vm.Id = "123")
Source-generated NavigateToDetail("123") β€” zero guesswork
GoBack Single page, multi-page GoBack(3), or PopToRoot()
Root navigation NavigateTo<DashboardViewModel>(relativeNavigation: false) β€” reset the stack
Navigation builder Fluent multi-segment: CreateBuilder().AddDetail(42).AddModal().Navigate()
Shell switching SwitchShell(new MainShell()) or SwitchShell<TShell>() via DI

πŸ’¬ Dialogs β€” IDialogs

Method Returns
Alert(title, message) Task
Confirm(title, message) Task<bool>
Prompt(title, message) Task<string?>
ActionSheet(title, cancel, destructive, ...buttons) Task<string>

Thread-safe β€” dispatches to UI thread automatically. Inject separately from INavigator for clean separation of concerns.

Alternative provider: Use Shiny.Maui.Shell.UxDiversDialogs for styled popup dialogs powered by UXDivers Popups β€” same IDialogs interface, no ViewModel changes needed.

πŸ“‘ Navigation Events

Event Fires Key Properties
Navigating Before navigation FromUri Β· FromViewModel Β· ToUri Β· NavigationType Β· Parameters
Navigated After page resolves ToUri Β· ToViewModel Β· NavigationType Β· Parameters

NavigationType: Push Β· SetRoot Β· GoBack Β· PopToRoot Β· SwitchShell

♻️ ViewModel Lifecycle

Interface Method Purpose
IPageLifecycleAware OnAppearing() / OnDisappearing() Page visibility hooks
INavigationConfirmation Task<bool> CanNavigate() Guard navigation (unsaved changes, etc.)
INavigationAware OnNavigatingFrom(params) Mutate parameters before leaving
IQueryAttributable ApplyQueryAttributes(params) Receive navigation parameters
IDisposable Dispose() Cleanup when page leaves the stack

⚑ Source Generation

Generated File What It Does
Routes.g.cs Static route constants β€” Routes.Detail
NavigationExtensions.g.cs Typed methods β€” NavigateToDetail(id, page)
NavigationBuilderNavExtensions.g.cs Typed builder methods β€” AddDetail(id, page)
NavigationBuilderExtensions.g.cs One-line DI β€” AddGeneratedMaps()

Invalid route names produce SHINY001 compiler errors. Disable individual outputs via MSBuild properties.

βœ… Zero Ceremony

  • One base class change β€” AppShell : ShinyShell β€” for deterministic BindingContext assignment
  • Page–ViewModel mapping with automatic BindingContext assignment
  • Drop-in [ShellMap] attribute replaces manual route registration

Getting Started

1. Install

dotnet add package Shiny.Maui.Shell

2. Configure MauiProgram.cs

With source generation (recommended):

builder
    .UseMauiApp<App>()
    .UseShinyShell(x => x.AddGeneratedMaps());

Manual registration:

builder
    .UseMauiApp<App>()
    .UseShinyShell(x => x
        .Add<MainPage, MainViewModel>(registerRoute: false) // pages in AppShell.xaml
        .Add<DetailPage, DetailViewModel>("Detail")
        .Add<SettingsPage, SettingsViewModel>("Settings")
    );

3. Set up AppShell

Your AppShell must inherit from ShinyShell instead of Shell:

AppShell.xaml:

<shiny:ShinyShell
    x:Class="MyApp.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:shiny="clr-namespace:Shiny;assembly=Shiny.Maui.Shell"
    xmlns:local="clr-namespace:MyApp"
    Title="MyApp">

    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" />

</shiny:ShinyShell>

AppShell.xaml.cs:

using Shiny;

namespace MyApp;

public partial class AppShell : ShinyShell
{
    public AppShell()
    {
        InitializeComponent();
    }
}

Pages defined in AppShell.xaml should use registerRoute: false.

4. Navigate

Inject INavigator into your ViewModels:

public class MyViewModel(INavigator navigator)
{
    // Route-based navigation with args
    await navigator.NavigateTo("Detail", args: [("ItemId", "123")]);

    // ViewModel-based navigation with strongly-typed configuration
    await navigator.NavigateTo<DetailViewModel>(vm => vm.ItemId = "123");

    // Source-generated strongly-typed method (preferred)
    await navigator.NavigateToDetail("123");

    // Root navigation β€” resets the stack
    await navigator.NavigateTo<DashboardViewModel>(relativeNavigation: false);

    // Go back with result
    await navigator.GoBack(("Result", selectedItem));

    // Go back multiple pages
    await navigator.GoBack(2);

    // Pop to root
    await navigator.PopToRoot();

    // Switch to a different Shell instance
    await navigator.SwitchShell(new MainAppShell());

    // Switch to a Shell resolved from DI
    await navigator.SwitchShell<MainAppShell>();

    // Fluent multi-segment navigation builder
    await navigator
        .CreateBuilder()
        .AddDetail(id: 42)
        .AddModal()
        .Navigate();

    // Pop back 2 pages, then push
    await navigator
        .CreateBuilder()
        .PopBack(2)
        .AddHome()
        .Navigate();

    // Navigate from root with builder
    await navigator
        .CreateBuilder(fromRoot: true)
        .AddDashboard()
        .AddDetail(id: 1)
        .Navigate();
}

Root navigation (relativeNavigation: false or CreateBuilder(fromRoot: true)) uses the // URI prefix, which requires the target route to be declared in your AppShell.xaml. Routes registered only via Routing.RegisterRoute or [ShellMap] cannot be navigated to from root. Add the page as a ShellContent in your Shell XAML and use registerRoute: false in [ShellMap].

If you're setting arguments on the ViewModel navigation, you should make them observable if they are bound on the Page.

5. Dialogs

Inject IDialogs for user-facing dialogs:

public class MyViewModel(IDialogs dialogs)
{
    // Alert
    await dialogs.Alert("Error", "Something went wrong");

    // Confirm
    if (await dialogs.Confirm("Delete?", "Are you sure?"))
    {
        // delete
    }

    // Prompt for text input
    var name = await dialogs.Prompt("Name", "Enter your name", placeholder: "John Doe");
    if (name != null)
    {
        // user entered a value
    }

    // Action sheet
    var choice = await dialogs.ActionSheet("Options", "Cancel", "Delete", "Edit", "Share");
}

6. UxDivers Dialogs (Optional)

Replace the default platform dialogs with styled popups from UXDivers Popups:

dotnet add package UXDivers.Popups.Maui

Add theme dictionaries to App.xaml:

<ResourceDictionary.MergedDictionaries>
    
    <uxd:DarkTheme xmlns:uxd="clr-namespace:UXDivers.Popups.Maui.Controls;assembly=UXDivers.Popups.Maui" />
    <uxd:PopupStyles xmlns:uxd="clr-namespace:UXDivers.Popups.Maui.Controls;assembly=UXDivers.Popups.Maui" />
</ResourceDictionary.MergedDictionaries>

Configure in MauiProgram.cs:

builder
    .UseMauiApp<App>()
    .UseUxDiversDialogs()       // Initialize UxDivers popup infrastructure
    .UseShinyShell(x => x
        .UseUxDiversDialogs()   // Register as IDialogs provider
        .AddGeneratedMaps()
    )

Your ViewModels continue using IDialogs as before β€” only the visual presentation changes.


Subscribe to Navigating and Navigated on INavigator for cross-cutting concerns like logging or analytics:

public class NavigationLogger(
    ILogger<NavigationLogger> logger,
    INavigator navigator
) : IMauiInitializeService
{
    public void Initialize(IServiceProvider services)
    {
        navigator.Navigating += (_, args) =>
            logger.LogInformation("Navigating from '{From}' to '{To}' ({Type})",
                args.FromUri, args.ToUri, args.NavigationType);

        navigator.Navigated += (_, args) =>
            logger.LogInformation("Navigated to '{To}' - ViewModel: {VM} ({Type})",
                args.ToUri, args.ToViewModel?.GetType().Name, args.NavigationType);
    }
}

// Register in MauiProgram.cs
builder.Services.AddSingleton<IMauiInitializeService, NavigationLogger>();

ViewModel Lifecycle

Implement these interfaces on your ViewModels as needed. Works just like Prism Library.

[ShellMap<DetailPage>("Detail")]
public partial class DetailViewModel(INavigator navigator, IDialogs dialogs) : ObservableObject,
    IQueryAttributable,
    IPageLifecycleAware,
    INavigationConfirmation,
    IDisposable
{
    [ShellProperty]
    [ObservableProperty]
    string itemId;

    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.TryGetValue(nameof(ItemId), out var id))
            ItemId = id?.ToString();
    }

    public void OnAppearing() { /* load data */ }
    public void OnDisappearing() { /* pause */ }

    public async Task<bool> CanNavigate()
    {
        if (!hasUnsavedChanges) return true;
        return await dialogs.Confirm("Unsaved Changes", "Discard changes?");
    }

    public void Dispose() { /* cleanup */ }
}

Source Generation

Decorate your ViewModels with [ShellMap] and [ShellProperty] to eliminate boilerplate:

Input:

[ShellMap<DetailPage>("Detail")]
public partial class DetailViewModel : ObservableObject
{
    [ShellProperty]
    public string ItemId { get; set; }

    [ShellProperty(required: false)]
    public int Page { get; set; }
}

Generated output:

// Routes.g.cs β€” constant name matches the route parameter
public static class Routes
{
    public const string Detail = "Detail";
}

// NavigationExtensions.g.cs β€” typed INavigator methods
public static class NavigationExtensions
{
    public static Task NavigateToDetail(this INavigator navigator, string itemId,
        int page = default, bool relativeNavigation = true)
    {
        return navigator.NavigateTo<DetailViewModel>(x =>
        {
            x.ItemId = itemId;
            x.Page = page;
        }, relativeNavigation);
    }
}

// NavigationBuilderNavExtensions.g.cs β€” typed INavigationBuilder methods
public static class NavigationBuilderNavExtensions
{
    public static INavigationBuilder AddDetail(this INavigationBuilder builder,
        string itemId, int page = default)
    {
        return builder.Add<DetailViewModel>(x => { x.ItemId = itemId; x.Page = page; });
    }
}

// NavigationBuilderExtensions.g.cs β€” uses string literals (not Routes.*)
public static class NavigationBuilderExtensions
{
    public static ShinyAppBuilder AddGeneratedMaps(this ShinyAppBuilder builder)
    {
        builder.Add<DetailPage, DetailViewModel>("Detail");
        return builder;
    }
}

Then use it:

// MauiProgram.cs - one line to register everything
builder.UseShinyShell(x => x.AddGeneratedMaps());

// Navigate with generated extension methods - no guesswork
await navigator.NavigateToDetail("123", page: 2);

// Fluent builder with generated extensions
await navigator.CreateBuilder().AddDetail("123", page: 2).Navigate();

Route Naming

The route parameter in [ShellMap] drives the generated constant and method names. It must be a valid C# identifier β€” invalid names produce a SHINY001 compiler error.

// Route drives the constant and method name
[ShellMap<HomePage>("Dashboard")]
// β†’ Routes.Dashboard = "Dashboard"
// β†’ NavigateToDashboard(...)

// No route β€” falls back to page type name without "Page" suffix
[ShellMap<HomePage>]
// β†’ Routes.Home = "HomePage"
// β†’ NavigateToHome(...)

Configuring Source Generation

Disable individual generated files via MSBuild properties:

<PropertyGroup>
    
    <ShinyMauiShell_GenerateRouteConstants>false</ShinyMauiShell_GenerateRouteConstants>

    
    <ShinyMauiShell_GenerateNavExtensions>false</ShinyMauiShell_GenerateNavExtensions>
</PropertyGroup>

NavigationBuilderExtensions.g.cs (AddGeneratedMaps()) is only generated when [ShellMap] attributes are detected and ShinyMauiShell_GenerateNavExtensions is not set to false. A SHINY002 warning is emitted if maps are detected but nav extensions are disabled.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Shiny.Maui.Shell:

Package Downloads
Shiny.Maui.Shell.UxDiversDialogs

Shiny MAUI Shell - Make .NET MAUI shell a pleasant experience

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on Shiny.Maui.Shell:

Repository Stars
shinyorg/shiny
.NET Framework for Backgrounding & Device Hardware Services (iOS, MacCatalyst, Android, & Windows)
Version Downloads Last Updated
6.0.3 172 4/29/2026
6.0.3-beta-0004 97 4/29/2026
6.0.3-beta-0003 106 4/29/2026
6.0.3-beta-0002 100 4/29/2026
6.0.2 104 4/29/2026
6.0.2-beta-0002 108 4/29/2026
6.0.1 105 4/28/2026
6.0.1-beta-0003 104 4/28/2026
6.0.1-beta-0002 101 4/27/2026
6.0.0 119 4/27/2026
6.0.0-beta-0001 102 4/26/2026
5.0.0 202 4/22/2026
5.0.0-beta-0001 102 4/21/2026
4.2.1 139 4/16/2026
4.2.0 105 4/15/2026
4.2.0-beta-0002 97 4/15/2026
4.1.0 182 4/14/2026
4.1.0-beta-0002 99 4/14/2026
4.0.0 109 4/11/2026
4.0.0-beta-0004 95 4/12/2026
Loading failed