Shell Releases
v6.1.1 - May 27, 2026
Section titled “v6.1.1 - May 27, 2026”NavigateTo<TViewModel>(configure) now applies configure before OnAppearing fires — configure callbacks were running after Shell.GoToAsync returned, which meant IPageLifecycleAware.OnAppearing saw a default-state ViewModel. A ViewModel that read a configure-set property in OnAppearing (e.g. to fetch a record by id and bounce back via Navigator.GoBack() if not found) would observe null/default, navigate away, and trigger an InvalidOperationException("Page BindingContext is not of type ...") against the stale page when the post-nav callback tried to apply. This also affected the source-generated NavigateTo{Name} methods since they delegate to NavigateTo<TViewModel>(vm => ...) internally. Callbacks now run before the page’s BindingContext is setShellNavigationConfigurator service — new internal-but-public service in Shiny.Infrastructure that holds queued configure callbacks. Enqueue<T>(Action<T>) returns an IDisposable so the navigator can roll back unconsumed entries on navigation failure; TryApply(object) pops the first matching-type entry. Applied at three sites — ShinyRouteFactory.GetOrCreate (routed pages), ShinyShell.OnNavigated (ShellContent-declared pages), and ShellNavigator.AppOnPageAppearing (fallback) — whichever fires first wins. FIFO + type-keyed lookup means interleaved navigations to different ViewModel types don’t collideMigration from v6.1
Section titled “Migration from v6.1”No code changes required. The fix is transparent to consumers — configure callbacks that previously appeared to work because the VM’s property setters fired INotifyPropertyChanged after OnAppearing will continue to work; the only observable difference is that the same callbacks now also reach OnAppearing synchronously, which closes a class of latent races.
v6.1 - May 20, 2026
Section titled “v6.1 - May 20, 2026”NavigateTo<TViewModel>(relativeNavigation: false) no longer hangs / crashes for ShellContent-declared routes — pages declared in AppShell.xaml as <ShellContent ContentTemplate="{DataTemplate ...}"> (with [ShellMap<TPage>(registerRoute: false)]) are constructed by MAUI via the DataTemplate and never go through ShinyRouteFactory.GetOrCreate. The previous implementation awaited the static ShinyRouteFactory.PageResolved event for completion — that event never fired for these pages, so the awaiter hung and the subscribed handler leaked. A subsequent unrelated navigation through a registered route would wake the leaked handler with the wrong page and throw InvalidOperationException("Page BindingContext is not of type '<original target VM>'") — on iOS surfaced asynchronously through NSAsyncSynchronizationContextDispatcher against a stale async void call site, which made the crash look like it originated from a much later, unrelated navigation. NavigateTo<TViewModel> now reads Shell.Current.CurrentPage.BindingContext directly after GoToAsync returns, which works uniformly for registered routes and ShellContent-declared routesNavigationBuilder.Navigate no longer suffers cross-navigation handler crosstalk — the builder previously subscribed to the same static PageResolved event, so two concurrent builders (or a builder running alongside a NavigateTo<TVM>) could fire each other’s configure callbacks against the wrong page. Navigate now walks Shell.Current.Navigation.NavigationStack after GoToAsync returns and applies each segment’s configure callback to the page at the corresponding stack index, with a warning log when the resolved page’s BindingContext doesn’t match the segment’s expected ViewModel typeShinyRouteFactory.PageResolved static event removed — the event lived in the Shiny.Infrastructure namespace and was only consumed internally by NavigateTo<TViewModel> and NavigationBuilder.Navigate. Both consumers have been rewritten and no longer need it. ShinyRouteFactory.GetOrCreate still resolves the page from DI and assigns BindingContext for registered routes — it just no longer broadcasts. If any external code was subscribing to this event for analytics or diagnostics, move that logic to INavigator.Navigated insteadconfigure callbacks now run after navigation completes — NavigateTo<TViewModel>(configure: vm => ...) and NavigationBuilder.Add<TViewModel>(vm => ...) previously invoked configure synchronously inside ShinyRouteFactory.GetOrCreate, before the page was rendered. They now run after Shell.GoToAsync returns, so values set via configure propagate to the UI through INotifyPropertyChanged rather than being pre-set on a brand-new VM. For most ViewModels this is invisible; if a value must be present before the first render, migrate to [ShellProperty] parameters — the source-generated NavigateTo{Name} methods set [ShellProperty] values before page realizationMigration from v6.0.3
Section titled “Migration from v6.0.3”No code changes are required for the common cases — NavigateTo<TViewModel>(relativeNavigation: false) against a ShellContent-declared route now works rather than hanging or eventually crashing, and existing configure callbacks continue to compile and run. Two corner cases worth checking:
-
External subscribers to
ShinyRouteFactory.PageResolved— the event was always part of theShiny.Infrastructurenamespace, but if you wired anything to it (logging, analytics, dev tooling), move that logic toINavigator.Navigated:// Before (v6.0.x)ShinyRouteFactory.PageResolved += (_, page) =>logger.LogDebug("Page resolved: {Type}", page.GetType());// After (v6.1)navigator.Navigated += (_, args) =>logger.LogDebug("Navigated to: {Uri}", args.ToUri); -
configurecallbacks that depend on pre-render timing — if your callback sets values that the first render must read synchronously (e.g., aCollectionView.ItemsSourcethat can’t tolerate a re-bind), migrate those values to[ShellProperty]parameters and use the generated typed navigation method.[ShellProperty]values are applied inside the page-creation flow, before the UI renders:// Before — pre-render timing relied on configureawait navigator.NavigateTo<DetailViewModel>(vm => vm.Items = preloadedItems);// After — declare as ShellProperty so the source-generated NavigateToDetail wires it pre-render[ShellProperty]public IReadOnlyList<Item> Items { get; set; }await navigator.NavigateToDetail(items: preloadedItems);
v6.0.3 - April 29, 2026
Section titled “v6.0.3 - April 29, 2026”IApplication resolved at initialization instead of constructor injection — ShinyShellNavigator now resolves IApplication from the service provider during Initialize instead of requiring it via constructor injection. This fixes DI resolution failures when IApplication is not yet available at service registration timeAiExtensions.g.cs is now generated independently when ShinyMauiShell_GenerateAiExtensions is true and the Microsoft.Extensions.AI package is present, regardless of the ShinyMauiShell_GenerateNavExtensions settingNavigateToRoute code — AI navigation for routes with no [ShellProperty] attributes now correctly calls NavigateTo<TViewModel>() without an empty lambda, fixing a compile error in the generated codePackage.targets is now packed as Shiny.Maui.Shell.targets instead of Shiny.Maui.Shell.SourceGenerators.targets, ensuring MSBuild properties like ShinyMauiShell_GenerateAiExtensions are correctly imported by consuming projects$(Configuration) — the packed analyzer DLL path now respects the active build configuration instead of being hardcoded to Release, fixing development-time source generation when building in DebugSHINY004 warning — new diagnostic warns when [ShellProperty] attributes have descriptions but the parent [ShellMap] has no description. AI tools cannot determine when to navigate to a route without a route-level descriptionShinyMauiShell_AiToolsClassName MSBuild property — customize the generated AiMauiShellTools class name via this new build property (default: AiMauiShellTools)v6.0.2 - April 29, 2026
Section titled “v6.0.2 - April 29, 2026”ShinyMauiShell_GenerateAiExtensions now defaults to disabled (opt-in) — previously defaulted to enabled, causing compile error SHINY003 when Microsoft.Extensions.AI was not referenced. Projects must now explicitly set ShinyMauiShell_GenerateAiExtensions to true to enable AI extensionsv6.0.1 - April 28, 2026
Section titled “v6.0.1 - April 28, 2026”AiMauiShellTools class — AI tools and prompt are now encapsulated in a generated AiMauiShellTools class that takes INavigator via constructor injection. Provides Prompt (pre-formatted route descriptions) and Tools (AITool[]) properties, plus GetAiToolApplicableGeneratedRoutes() and NavigateToRoute() instance methods. Designed for DI registration as a singletonAddAiTools() extension — new generated extension method on ShinyAppBuilder that registers AiMauiShellTools as a singleton: builder.UseShinyShell(x => x.AddGeneratedMaps().AddAiTools())ShinyMauiShell_AiToolsClassName MSBuild property — customize the generated AI tools class name (default: AiMauiShellTools)ShinyMauiShell_GenerateAiExtensions enables AI tool generation when set to true. Requires Microsoft.Extensions.AI package (SHINY003 error if missing)AiMauiShellTools class — GetAiToolApplicableGeneratedRoutes(), NavigateToRoute(), AiRoutePrompt(), and GetAiTools() are no longer extension methods on INavigator. Instead, inject AiMauiShellTools and use its Prompt, Tools, and instance methods. GetGeneratedRouteInfo() remains as a static extension on INavigatorMigration from v6.0
Section titled “Migration from v6.0”AI tools have moved from INavigator extension methods to the injectable AiMauiShellTools class:
// Before (v6.0) — extension methods on INavigatorvar tools = navigator.GetAiTools();var prompt = navigator.AiRoutePrompt();var routes = navigator.GetAiToolApplicableGeneratedRoutes();
// After (v6.0.1) — inject AiMauiShellTools// MauiProgram.csbuilder.UseShinyShell(x => x .AddGeneratedMaps() .AddAiTools() // registers AiMauiShellTools as singleton);
// ViewModelpublic class ChatViewModel(AiMauiShellTools aiTools){ var tools = aiTools.Tools; var prompt = aiTools.Prompt; var routes = aiTools.GetAiToolApplicableGeneratedRoutes();}AI extensions are now enabled by default. If you don’t use AI features and don’t have Microsoft.Extensions.AI installed, either install the package or explicitly disable:
<PropertyGroup> <ShinyMauiShell_GenerateAiExtensions>false</ShinyMauiShell_GenerateAiExtensions></PropertyGroup>v6.0 - April 25, 2026
Section titled “v6.0 - April 25, 2026”[ShellMap] now accepts a description parameter and [ShellProperty] now accepts description as the first parameter. When provided, the source generator emits XML doc comments, [System.ComponentModel.Description] attributes on methods and parameters, enabling AI tooling and IDE discoverabilityGetGeneratedRouteInfo() extension method — new generated AiExtensions.g.cs produces a GetGeneratedRouteInfo() method on INavigator that returns an array of GeneratedRouteInfo records containing route names, descriptions, and parameter metadata. Designed for AI agents and tooling to discover available navigation routes at runtimeDisableShellFlyoutSwipeHandler — new opt-in custom handler that disables the Shell flyout swipe gesture while keeping the hamburger button functional. Call DisableShellFlyoutSwipeHandler.Register() in MauiProgram.cs to enable. Supported on Android (locks DrawerLayout), iOS/Mac Catalyst (disables UIPanGestureRecognizer), and no-op on WindowsShellPropertyAttribute parameter order changed — description is now the first parameter (string? description = null, bool required = true), replacing the previous (bool required = true) signature. Positional usage like [ShellProperty(true)] must change to [ShellProperty(required: true)]GeneratedRouteInfo record — new Shiny.Infrastructure.GeneratedRouteInfo and GeneratedRouteParameter records provide structured route metadata for runtime inspectionIQueryAttributable no longer required for [ShellProperty] — the source-generated navigation methods (NavigateTo{Name}() and AI NavigateToRoute()) now set [ShellProperty] properties directly on the ViewModel instance. IQueryAttributable is only needed if you use string-based NavigateTo(route, args) with tuple parametersMigration from v5.x
Section titled “Migration from v5.x”The ShellPropertyAttribute constructor parameter order changed. Update any positional bool arguments to use named syntax:
// Before (v5.x)[ShellProperty(true)]public string Name { get; set; }
[ShellProperty(false)]public string OptionalNote { get; set; }
// After (v6.0)[ShellProperty(required: true)]public string Name { get; set; }
[ShellProperty(required: false)]public string OptionalNote { get; set; }
// New: add descriptions for AI tooling[ShellProperty("The user's display name")]public string Name { get; set; }
[ShellProperty("Optional note text", required: false)]public string OptionalNote { get; set; }Update [ShellMap] to include descriptions:
// Before (v5.x)[ShellMap<DetailPage>("Detail")]
// After (v6.0) — description is optional[ShellMap<DetailPage>("Detail", description: "Navigate to the detail page")]Disable Flyout Swipe Setup
Section titled “Disable Flyout Swipe Setup”using Shiny.Handlers;
// In MauiProgram.cs, before builder.Build()DisableShellFlyoutSwipeHandler.Register();v5.0 - April 21, 2026
Section titled “v5.0 - April 21, 2026”INavigator now supports SetTabBadge(string route, int value), SetTabBadge<TViewModel>(int value), ClearTabBadge(string route), and ClearTabBadge<TViewModel>(), enabling numeric badges on Shell tabs by route or ViewModel mappingNavigate attached properties enable route-based navigation directly from XAML on Button, MenuItem, and ToolbarItem, including support for a single parameter pair or a NavigationParameters collectionPlatformNotSupportedException instead of silently doing nothingv4.2 - April 15, 2026
Section titled “v4.2 - April 15, 2026”Shiny.Maui.Shell.UxDiversDialogs package provides an alternative IDialogs implementation powered by UXDivers Popups. Drop-in replacement — no ViewModel changes needed, only the visual presentation changesUseUxDiversDialogs() extension methods — two extension methods for setup: MauiAppBuilder.UseUxDiversDialogs() initializes the UxDivers popup infrastructure, and ShinyAppBuilder.UseUxDiversDialogs() registers the IDialogs implementationUxDivers Dialogs Setup
Section titled “UxDivers Dialogs Setup”Install the package and the UxDivers dependency:
dotnet add package UXDivers.Popups.MauiAdd theme dictionaries to App.xaml:
<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" />Configure in MauiProgram.cs:
builder .UseMauiApp<App>() .UseUxDiversDialogs() // Initialize UxDivers popup infrastructure .UseShinyShell(x => x .UseUxDiversDialogs() // Register as IDialogs provider .AddGeneratedMaps() )| IDialogs Method | UxDivers Popup Used |
|---|---|
Alert | SimpleActionPopup (single button) |
Confirm | SimpleActionPopup (two buttons) |
Prompt | FormPopup with single FormField |
ActionSheet | OptionSheetPopup with OptionSheetItem per button |
v4.1 - April 13, 2026
Section titled “v4.1 - April 13, 2026”AddGeneratedMaps() is no longer generated when nav extensions are disabled — setting ShinyMauiShell_GenerateNavExtensions to false now also prevents generation of NavigationBuilderExtensions.g.cs (AddGeneratedMaps). A SHINY002 warning is emitted when [ShellMap] attributes are detected but nav extensions are disabledAddGeneratedMaps() is no longer generated when no maps exist — NavigationBuilderExtensions.g.cs is now only produced when at least one [ShellMap] attribute is present, removing the empty stub that was previously always emittedMigration from v4.0
Section titled “Migration from v4.0”- If you relied on
AddGeneratedMaps()being available before adding any[ShellMap]attributes, add at least one[ShellMap]to generate the method - If you set
ShinyMauiShell_GenerateNavExtensionstofalsebut still usedAddGeneratedMaps(), either remove the property or replaceAddGeneratedMaps()with manual.Add<TPage, TViewModel>()calls
v4.0 - April 11, 2026
Section titled “v4.0 - April 11, 2026”INavigationBuilder — fluent builder for multi-segment Shell navigation URIs. Chain Add<TViewModel>(), Add(string), and PopBack() calls, then execute with a single Navigate(). Supports relative, root (fromRoot: true), and mixed pop/push patterns like ../../Page1/Page2INavigator.CreateBuilder(bool fromRoot) — factory method to create an INavigationBuilder instance. Pass fromRoot: true to build absolute URIs prefixed with //INavigationBuilder extensions — source generator now produces NavigationBuilderNavExtensions.g.cs with typed Add{Name}() methods for each [ShellMap] ViewModel, enabling fluent chains like .AddDetail(id: 42).AddModal().Navigate()relativeNavigation parameter — NavigateTo(string) and NavigateTo<TViewModel>() now accept bool relativeNavigation = true. When false, the URI is prefixed with // for root navigation. Replaces the previous SetRoot<TViewModel>() methodNavigationExtensions — all generated NavigateTo{Name}() methods now include a bool relativeNavigation = true parameterMigration from v3.x
Section titled “Migration from v3.x”SetRoot<TViewModel>()has been removed — useNavigateTo<TViewModel>(relativeNavigation: false)instead- Calls to
NavigateTothat pass tuple args as positional parameters may need theargs:named parameter to disambiguate from the newbool relativeNavigationparameter
// Before (v3.x)await navigator.SetRoot<HomeViewModel>(vm => vm.Message = "Hello");await navigator.NavigateTo("details", ("Id", 42));
// After (v4.0)await navigator.NavigateTo<HomeViewModel>(vm => vm.Message = "Hello", relativeNavigation: false);await navigator.NavigateTo("details", args: [("Id", 42)]);
// New: Navigation Builderawait navigator .CreateBuilder() .AddDetail(id: 42) .AddModal() .Navigate();
// New: Pop back and pushawait navigator .CreateBuilder() .PopBack(2) .AddHome() .Navigate();v3.2.1 - April 8, 2026
Section titled “v3.2.1 - April 8, 2026”MauiMainThread now bypasses MainThread.InvokeOnMainThreadAsync on macOS and Linux, where MAUI’s implementation fails or deadlocks. INavigator and IDialogs dispatch calls now work reliably on both platformsIMainThread interface — thread-marshalling abstraction used internally by ShellNavigator and ShellDialogs, now registered as a singleton so you can inject it directly instead of using Microsoft.Maui.ApplicationModel.MainThreadShellServices record — convenience aggregate of INavigator, IDialogs, and IMainThread — inject a single parameter when a ViewModel needs most of themUseDialogs<TDialog>() — plug in a custom IDialogs implementation via UseShinyShell(x => x.UseDialogs<MyDialogs>()). The default ShellDialogs registration now uses TryAddSingleton so user overrides always winv3.2 - April 1, 2026
Section titled “v3.2 - April 1, 2026”ShinyShell class that overrides OnNavigated to deterministically set the initial page’s BindingContext via Shell’s own lifecycle, eliminating a race condition where the Application.PageAppearing event could fire before the handler was registeredMigration from v3.1
Section titled “Migration from v3.1”Your AppShell (and any other Shell subclass) must now inherit from ShinyShell instead of Shell:
AppShell.xaml — change root element:
<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"> <!-- ... --></shiny:ShinyShell>AppShell.xaml.cs — change base class:
using Shiny;
public partial class AppShell : ShinyShell{ public AppShell() { InitializeComponent(); }}v3.1.1 - March 17, 2026
Section titled “v3.1.1 - March 17, 2026”INavigationConfirmation no longer triggers during programmatic navigation — CanNavigate() is now only called for user-initiated navigation (back button, tab switches), not when navigating via INavigator methodsv3.1 - March 11, 2026
Section titled “v3.1 - March 11, 2026”SwitchShell — swap the entire active Shell at runtime via INavigator.SwitchShell(shell) or INavigator.SwitchShell<TShell>() (DI-resolved). Fires Navigating/Navigated events with NavigationType.SwitchShell and respects INavigationAware.OnNavigatingFromSwitchShell<TShell>() — generic overload resolves the Shell from the DI container, enabling constructor-injected Shell instancesNavigationType.SwitchShell — new enum value for tracking shell switches in navigation events and analyticsv3.0 - March 11, 2026
Section titled “v3.0 - March 11, 2026”Alert, Confirm, Prompt, and ActionSheet methods, injected separately from INavigator for clean separation of concernsPrompt dialog — display a text input dialog with customizable keyboard type, placeholder, initial value, and max lengthActionSheet dialog — display a multi-option action sheet with cancel and destructive action supportAlert and Confirm moved from INavigator to IDialogs — improves testability and follows interface segregationMigration from v2.x
Section titled “Migration from v2.x”INavigator.Alert()andINavigator.Confirm()have been removed — injectIDialogsinstead- Replace
navigator.Alert(...)withdialogs.Alert(...)andnavigator.Confirm(...)withdialogs.Confirm(...) - Add
IDialogsto your ViewModel constructor parameters where dialogs are used IDialogsis automatically registered byUseShinyShell()— no additional setup required
// Before (v2.x)public class MyViewModel(INavigator navigator){ await navigator.Alert("Error", "Something went wrong"); bool ok = await navigator.Confirm("Delete?", "Are you sure?");}
// After (v3.0)public class MyViewModel(INavigator navigator, IDialogs dialogs){ await dialogs.Alert("Error", "Something went wrong"); bool ok = await dialogs.Confirm("Delete?", "Are you sure?");
// New capabilities var name = await dialogs.Prompt("Name", "Enter your name"); var choice = await dialogs.ActionSheet("Options", "Cancel", null, "Edit", "Share");}ShinyMauiShell_GenerateRouteConstants or navigation extensions via ShinyMauiShell_GenerateNavExtensions MSBuild properties (empty/missing = enabled, false = disabled)route parameter in [ShellMap] now drives the generated constant name and navigation method name (e.g., [ShellMap<HomePage>("Dashboard")] → Routes.Dashboard, NavigateToDashboard)AddGeneratedMaps() was always generated — even before any [ShellMap] attributes existed — so you could wire up MauiProgram.cs immediately (changed in v4.1: now requires at least one [ShellMap])AddGeneratedMaps() now uses inline string literals instead of Routes.* constants, so it works correctly even when route constant generation is disabledPage suffix (e.g., [ShellMap<HomePage>] → Routes.Home)Migration from v2.1
Section titled “Migration from v2.1”- Route constant names may change if you specified explicit routes — e.g.,
Routes.Homefor[ShellMap<HomePage>("Dashboard")]is nowRoutes.Dashboard - Navigation extension method names change similarly —
NavigateToHomebecomesNavigateToDashboard - Routes with invalid C# identifiers (hyphens, spaces, leading digits) now produce compile errors — rename them to valid identifiers