maui-shell-navigation

.NET MAUI Shell Navigation

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "maui-shell-navigation" with this command: npx skills add davidortinau/maui-skills/davidortinau-maui-skills-maui-shell-navigation

.NET MAUI Shell Navigation

Shell Visual Hierarchy

Shell uses a four-level hierarchy. Each level wraps the one below it:

Shell ├── FlyoutItem / TabBar (top-level navigation grouping) │ ├── Tab (bottom-tab grouping) │ │ ├── ShellContent (page slot; points to a ContentPage) │ │ └── ShellContent (creates top tabs within a bottom tab) │ └── Tab └── FlyoutItem / TabBar

  • FlyoutItem – appears in the flyout menu. Contains one or more Tab children.

  • TabBar – bottom tab bar with no flyout entry. Use when the app has no flyout.

  • Tab – groups ShellContent objects. Multiple ShellContent in one Tab produces top tabs.

  • ShellContent – each represents a ContentPage .

Implicit Conversion

You can omit intermediate wrappers. Shell auto-wraps:

You write Shell creates

ShellContent only FlyoutItem > Tab > ShellContent

Tab only FlyoutItem > Tab

ShellContent in TabBar

TabBar > Tab > ShellContent

This keeps simple apps concise while allowing full control when needed.

AppShell.xaml Setup

<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:views="clr-namespace:MyApp.Views" x:Class="MyApp.AppShell" FlyoutBehavior="Flyout">

&#x3C;FlyoutItem Title="Animals" Icon="animals.png">
    &#x3C;Tab Title="Cats">
        &#x3C;ShellContent Title="Domestic"
                      ContentTemplate="{DataTemplate views:DomesticCatsPage}" />
        &#x3C;ShellContent Title="Wild"
                      ContentTemplate="{DataTemplate views:WildCatsPage}" />
    &#x3C;/Tab>
    &#x3C;Tab Title="Dogs" Icon="dogs.png">
        &#x3C;ShellContent ContentTemplate="{DataTemplate views:DogsPage}" />
    &#x3C;/Tab>
&#x3C;/FlyoutItem>

&#x3C;TabBar>
    &#x3C;ShellContent Title="Home" Icon="home.png"
                  ContentTemplate="{DataTemplate views:HomePage}" />
    &#x3C;ShellContent Title="Settings" Icon="settings.png"
                  ContentTemplate="{DataTemplate views:SettingsPage}" />
&#x3C;/TabBar>

</Shell>

ContentTemplate and Lazy Loading

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

Tab Configuration

Bottom Tabs

Multiple ShellContent (or Tab ) children inside a TabBar or FlyoutItem

produce bottom tabs.

Top Tabs

Multiple ShellContent children inside a single Tab produce top tabs within that bottom tab:

<Tab Title="Photos"> <ShellContent Title="Recent" ContentTemplate="{DataTemplate views:RecentPage}" /> <ShellContent Title="Favorites" ContentTemplate="{DataTemplate views:FavoritesPage}" /> </Tab>

TabBar Appearance (Attached Properties)

Set these on any page or Shell element:

Attached Property Type Purpose

Shell.TabBarBackgroundColor

Color

Tab bar background

Shell.TabBarForegroundColor

Color

Foreground / selected icon color

Shell.TabBarTitleColor

Color

Selected tab title color

Shell.TabBarUnselectedColor

Color

Unselected tab icon/title color

Shell.TabBarDisabledColor

Color

Disabled tab color

Shell.TabBarIsVisible

bool

Show/hide the tab bar

<ContentPage Shell.TabBarIsVisible="False" ... />

Flyout Configuration

FlyoutBehavior

Set on Shell :

<Shell FlyoutBehavior="Flyout"> ... </Shell>

Values: Disabled , Flyout , Locked .

FlyoutDisplayOptions

Controls how a FlyoutItem 's children appear in the flyout:

<FlyoutItem Title="Animals" FlyoutDisplayOptions="AsMultipleItems"> <Tab Title="Cats" ... /> <Tab Title="Dogs" ... /> </FlyoutItem>

  • AsSingleItem (default) – one flyout entry for the group.

  • AsMultipleItems – each child Tab gets its own flyout entry.

Flyout Item Template

Customize appearance with Shell.ItemTemplate . BindingContext exposes Title

and FlyoutIcon (FlyoutItem) or Text and IconImageSource (MenuItem):

<Shell.ItemTemplate> <DataTemplate> <Grid ColumnDefinitions="Auto,*" Padding="10"> <Image Source="{Binding FlyoutIcon}" HeightRequest="24" /> <Label Grid.Column="1" Text="{Binding Title}" VerticalTextAlignment="Center" /> </Grid> </DataTemplate> </Shell.ItemTemplate>

Replacing Flyout Content

<Shell.FlyoutContent> <CollectionView BindingContext="{x:Reference shell}" ItemsSource="{Binding FlyoutItems}" /> </Shell.FlyoutContent>

MenuItem (non-navigation flyout entries)

<MenuItem Text="Log Out" Command="{Binding LogOutCommand}" IconImageSource="logout.png" />

Route Registration

Shell visual hierarchy items have implicit routes derived from their Route

property (or type name). Detail pages not in the hierarchy must be registered:

// In AppShell constructor or MauiProgram Routing.RegisterRoute("animaldetails", typeof(AnimalDetailsPage)); Routing.RegisterRoute("editanimal", typeof(EditAnimalPage));

Gotcha: Duplicate route names throw ArgumentException at registration time. Every route must be unique across the entire app.

Navigation with GoToAsync

All programmatic navigation goes through Shell.Current.GoToAsync :

// Absolute – navigate to a specific place in the hierarchy await Shell.Current.GoToAsync("//animals/cats/domestic");

// Relative – push a registered page onto the navigation stack await Shell.Current.GoToAsync("animaldetails");

// With query string await Shell.Current.GoToAsync($"animaldetails?id={animal.Id}");

Absolute vs Relative Routes

Prefix Meaning

//

Absolute route from Shell root

(none) Relative; pushes onto the current nav stack

..

Go back one level in the navigation stack

../

Go back then navigate forward

// Go back one page await Shell.Current.GoToAsync("..");

// Go back two pages await Shell.Current.GoToAsync("../..");

// Go back one page, then navigate to edit await Shell.Current.GoToAsync("../editanimal");

Gotcha: Relative routes work only for pages registered with Routing.RegisterRoute . You cannot push visual-hierarchy pages as relative routes.

Query Parameters

QueryProperty Attribute

[QueryProperty(nameof(AnimalId), "id")] public partial class AnimalDetailsPage : ContentPage { public string AnimalId { get; set; } }

// Navigate with query string: await Shell.Current.GoToAsync($"animaldetails?id={animal.Id}");

IQueryAttributable Interface

Preferred for ViewModels — gives you all parameters in one call:

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

The interface works on the page itself or on any object set as the page's BindingContext .

Passing Complex Objects

Use ShellNavigationQueryParameters (dictionary of string → object ) to pass objects without serializing to strings:

var parameters = new ShellNavigationQueryParameters { { "animal", selectedAnimal } // pass the object directly }; await Shell.Current.GoToAsync("animaldetails", parameters);

Receive via IQueryAttributable :

public void ApplyQueryAttributes(IDictionary<string, object> query) { Animal = query["animal"] as Animal; }

Navigation Events

Override in your AppShell :

protected override void OnNavigating(ShellNavigatingEventArgs args) { base.OnNavigating(args); if (hasUnsavedChanges && args.Source == ShellNavigationSource.Pop) args.Cancel(); // prevent leaving }

protected override void OnNavigated(ShellNavigatedEventArgs args) { base.OnNavigated(args); // args.Current, args.Previous, args.Source }

For async checks, use args.GetDeferral() → do work → deferral.Complete() .

ShellNavigationSource values: Push , Pop , PopToRoot , Insert , Remove , ShellItemChanged , ShellSectionChanged , ShellContentChanged , Unknown .

Inspecting Navigation State

// Current URI location ShellNavigationState state = Shell.Current.CurrentState; string location = state.Location.ToString(); // e.g. "//animals/cats/domestic"

// Current page Page page = Shell.Current.CurrentPage;

// Navigation stack of the current tab IReadOnlyList<Page> stack = Shell.Current.Navigation.NavigationStack;

Back Button Behavior

Customize the back button per page:

<Shell.BackButtonBehavior> <BackButtonBehavior Command="{Binding BackCommand}" IconOverride="back_arrow.png" TextOverride="Cancel" /> </Shell.BackButtonBehavior>

Properties: Command , CommandParameter , IconOverride , TextOverride , IsVisible , IsEnabled .

Common Gotchas

  • Duplicate route names – Routing.RegisterRoute throws ArgumentException

if a route name is already registered or matches a visual hierarchy route.

  • Relative routes require registration – you cannot GoToAsync("somepage")

unless somepage was registered with Routing.RegisterRoute . Visual hierarchy pages use absolute // routes.

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

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

  • GoToAsync is async – always await it. Fire-and-forget navigation causes race conditions and can silently fail.

  • Route hierarchy matters – absolute routes must match the full path through the visual hierarchy (//FlyoutItem/Tab/ShellContent ).

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

maui-performance

No summary provided by upstream source.

Repository SourceNeeds Review
General

maui-data-binding

No summary provided by upstream source.

Repository SourceNeeds Review
General

maui-dependency-injection

No summary provided by upstream source.

Repository SourceNeeds Review
General

maui-permissions

No summary provided by upstream source.

Repository SourceNeeds Review