mcpField
M
maui-shell-navigation

maui-shell-navigation

github.com/davidortinauDevelopment17664 installsUpdated 17w ago
Repository

About maui-shell-navigation

A .NET MAUI skill for designing navigation with Shell. It covers on-demand page creation, query-based data passing, asynchronous navigation guards, and common routing and navigation pitfalls.

Key features

  1. 01Create pages on demand with ContentTemplate
  2. 02Pass data with IQueryAttributable
  3. 03Pass complex objects with ShellNavigationQueryParameters
  4. 04Defer navigation for asynchronous checks
  5. 05Register unique routes and await navigation

Use cases

  1. 01Reduce page creation during Shell startup
  2. 02Pass identifiers or objects to a destination page
  3. 03Confirm before leaving with unsaved changes
  4. 04Diagnose route mismatches or navigation race conditions

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/davidortinau/maui-skills --skill maui-shell-navigation

After installing, type/maui-shell-navigationin Claude Code to confirm it loaded.

Bundled filesreferences/shell-navigation-api.md

Details

From the project README

.NET MAUI Shell Navigation

Key decisions

ContentTemplate — always use it

Always use ContentTemplate with DataTemplate so pages are created on demand. Using Content directly creates all pages during Shell init, hurting startup time.

<!-- ✅ Lazy — page created on first navigation -->
<ShellContent ContentTemplate="{DataTemplate views:HomePage}" />

<!-- ❌ Eager — page created at Shell startup -->
<ShellContent>
    <views:HomePage />
</ShellContent>
Passing data — prefer IQueryAttributable over QueryProperty

IQueryAttributable gives you all parameters in one call and works on ViewModels:

public class AnimalDetailsViewModel : ObservableObject, IQueryAttributable
{
    public void ApplyQueryAttributes(IDictionary<string, object> query)
    {
        if (query.TryGetValue("id", out var id))
            AnimalId = id.ToString();
    }
}

For complex objects, use ShellNavigationQueryParameters to avoid serializing:

var parameters = new ShellNavigationQueryParameters
{
    { "animal", selectedAnimal }
};
await Shell.Current.GoToAsync("animaldetails", parameters);
Guarding navigation — async deferral pattern

Use GetDeferral() for async checks (e.g., "save unsaved changes?"):

protected override async void OnNavigating(ShellNavigatingEventArgs args)
{
    base.OnNavigating(args);
    if (hasUnsavedChanges && args.Source == ShellNavigationSource.Pop)
    {
        var deferral = args.GetDeferral();
        bool discard = await ShowConfirmationDialog();
        if (!discard)
            args.Cancel();
        deferral.Complete();
    }
}

Common gotchas

  1. Duplicate route names — Routing.RegisterRoute throws ArgumentException if a route name is already registered or matches a visual hierarchy route. Every route must be unique across the entire app.

  2. Relative routes require registration — you cannot GoToAsync("somepage") unless somepage was registered with Routing.RegisterRoute. Visual hierarchy pages use absolute // routes instead.

  3. Pages are created on demand — when using ContentTemplate, the page constructor runs only on first navigation. Don't assume pages exist at startup.

  4. Tab.Stack is read-only — you cannot manipulate the navigation stack directly; use GoToAsync for all navigation changes.

  5. GoToAsync is async — always await it — fire-and-forget navigation causes race conditions and can silently fail:

    // ❌ Fire-and-forget — race conditions
    Shell.Current.GoToAsync("details");
    
    // ✅ Always await
    await Shell.Current.GoToAsync("details");
    
  6. Route hierarchy matters — absolute routes must match the full path through the visual hierarchy (//FlyoutItem/Tab/ShellContent). Getting the path wrong produces silent no-ops, not exceptions.

Comments

Markdown supported