Loading...
Loading...
.NET MAUI Shell navigation guidance — Shell visual hierarchy, AppShell setup, tab bars, flyout menus, URI-based navigation with GoToAsync, route registration, query parameters, back navigation, and navigation events. Use when building or modifying Shell-based MAUI apps, adding pages/routes, configuring tabs or flyout, or implementing navigation with data passing.
npx skill4agent add davidortinau/maui-skills maui-shell-navigationShell
├── 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 / TabBarTabShellContentShellContentTabContentPage| You write | Shell creates |
|---|---|
| |
| |
| |
<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">
<FlyoutItem Title="Animals" Icon="animals.png">
<Tab Title="Cats">
<ShellContent Title="Domestic"
ContentTemplate="{DataTemplate views:DomesticCatsPage}" />
<ShellContent Title="Wild"
ContentTemplate="{DataTemplate views:WildCatsPage}" />
</Tab>
<Tab Title="Dogs" Icon="dogs.png">
<ShellContent ContentTemplate="{DataTemplate views:DogsPage}" />
</Tab>
</FlyoutItem>
<TabBar>
<ShellContent Title="Home" Icon="home.png"
ContentTemplate="{DataTemplate views:HomePage}" />
<ShellContent Title="Settings" Icon="settings.png"
ContentTemplate="{DataTemplate views:SettingsPage}" />
</TabBar>
</Shell>ContentTemplateDataTemplateContentShellContentTabTabBarFlyoutItemShellContentTab<Tab Title="Photos">
<ShellContent Title="Recent" ContentTemplate="{DataTemplate views:RecentPage}" />
<ShellContent Title="Favorites" ContentTemplate="{DataTemplate views:FavoritesPage}" />
</Tab>| Attached Property | Type | Purpose |
|---|---|---|
| | Tab bar background |
| | Foreground / selected icon color |
| | Selected tab title color |
| | Unselected tab icon/title color |
| | Disabled tab color |
| | Show/hide the tab bar |
<ContentPage Shell.TabBarIsVisible="False" ... />Shell<Shell FlyoutBehavior="Flyout"> ... </Shell>DisabledFlyoutLockedFlyoutItem<FlyoutItem Title="Animals" FlyoutDisplayOptions="AsMultipleItems">
<Tab Title="Cats" ... />
<Tab Title="Dogs" ... />
</FlyoutItem>AsSingleItemAsMultipleItemsTabShell.ItemTemplateTitleFlyoutIconTextIconImageSource<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><Shell.FlyoutContent>
<CollectionView BindingContext="{x:Reference shell}"
ItemsSource="{Binding FlyoutItems}" />
</Shell.FlyoutContent><MenuItem Text="Log Out"
Command="{Binding LogOutCommand}"
IconImageSource="logout.png" />Route// In AppShell constructor or MauiProgram
Routing.RegisterRoute("animaldetails", typeof(AnimalDetailsPage));
Routing.RegisterRoute("editanimal", typeof(EditAnimalPage));ArgumentExceptionShell.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}");| 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");Routing.RegisterRoute[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}");public class AnimalDetailsViewModel : ObservableObject, IQueryAttributable
{
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.TryGetValue("id", out var id))
AnimalId = id.ToString();
}
}BindingContextShellNavigationQueryParametersstringobjectvar parameters = new ShellNavigationQueryParameters
{
{ "animal", selectedAnimal } // pass the object directly
};
await Shell.Current.GoToAsync("animaldetails", parameters);IQueryAttributablepublic void ApplyQueryAttributes(IDictionary<string, object> query)
{
Animal = query["animal"] as Animal;
}AppShellprotected 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
}args.GetDeferral()deferral.Complete()ShellNavigationSourcePushPopPopToRootInsertRemoveShellItemChangedShellSectionChangedShellContentChangedUnknown// 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;<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back_arrow.png"
TextOverride="Cancel" />
</Shell.BackButtonBehavior>CommandCommandParameterIconOverrideTextOverrideIsVisibleIsEnabledRouting.RegisterRouteArgumentExceptionGoToAsync("somepage")somepageRouting.RegisterRoute//ContentTemplateGoToAsyncawait//FlyoutItem/Tab/ShellContent