What Is Shell Navigation?
Shell is a single AppShell.xaml file that hosts an app's entire visual hierarchy — flyout menu, bottom tabs, and pages — and exposes URI-based navigation through Shell.Current.GoToAsync. Instead of manually pushing pages onto a NavigationPage stack, you declare the structure once and navigate by route string, which also gives you deep linking and back-stack management for free.
Cricket analogy: Shell is like the pre-set batting order and fielding plan for a T20 franchise such as Kolkata Knight Riders: the structure is decided before the toss, so nobody rebuilds it from scratch each match, the same way Shell's flyout and tabs are declared once.
Registering Routes
There are two kinds of routes. Implicit routes are declared as ShellContent entries directly in AppShell.xaml and are auto-registered when the app starts, forming the visible flyout/tab structure. Explicit routes are registered in code with Routing.RegisterRoute("route", typeof(Page)) for detail pages that aren't part of the permanent visual tree but still need to be reachable — for example a ProductDetailPage opened only from a list.
Cricket analogy: It's like the ICC pre-approving venues for a World Cup: Eden Gardens and the MCG are on the list automatically, while a brand-new stadium must be explicitly certified before a match can be scheduled there, just as RegisterRoute explicitly adds a page.
<!-- AppShell.xaml : implicit routes -->
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:GroceryApp.Views"
x:Class="GroceryApp.AppShell">
<FlyoutItem Title="Shop">
<ShellContent Title="Home"
ContentTemplate="{DataTemplate views:HomePage}"
Route="home" />
</FlyoutItem>
<FlyoutItem Title="Cart">
<ShellContent Title="Cart"
ContentTemplate="{DataTemplate views:CartPage}"
Route="cart" />
</FlyoutItem>
</Shell>public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
// Explicit route: not part of the flyout, reached only via GoToAsync
Routing.RegisterRoute("product/detail", typeof(ProductDetailPage));
}
}
// Later, from anywhere in the app:
await Shell.Current.GoToAsync($"product/detail?productId={product.Id}");Absolute routes start with "//" (e.g. "//cart") and reset the navigation stack to that destination. Relative routes (no leading "//") push the page onto the current stack, so GoToAsync("product/detail") keeps the flyout page underneath it.
Passing Data with Query Parameters
Simple values travel through the URI's query string and are mapped onto a page with the [QueryProperty("PropertyName", "queryKey")] attribute, which sets a matching bindable property when the page is navigated to. For complex objects — an entity you don't want to re-fetch or serialize — implement IQueryAttributable and read the raw object out of the IDictionary<string,object> passed to ApplyQueryAttributes, populated via GoToAsync's ShellNavigationQueryParameters overload.
Cricket analogy: QueryProperty is like a scorecard passing a batter's exact strike rate and not-out status straight to the broadcast graphics team, so the next screen already has the numbers instead of recalculating them from scratch.
Programmatic Navigation and the Back Stack
GoToAsync("..") pops a single page off the current stack, while GoToAsync("//route") clears the stack and resets to a new root, and Shell.Current.Navigation exposes the stack directly if you need to inspect or manipulate it further. Unlike a plain NavigationPage, Shell can jump between flyout items and tabs in one navigation call, since routes are resolved against the whole app's route table rather than a single stack.
Cricket analogy: GoToAsync('..') is like a captain reviewing the previous over's field placement before the next ball, popping back one state, while '//route' is resetting the entire fielding formation for a fresh innings.
Registering the same route string twice, or calling GoToAsync with a route that was never registered, throws an exception at runtime rather than failing at compile time. Route strings are also case-sensitive, so "Product/Detail" and "product/detail" are treated as different routes — always match casing exactly between RegisterRoute and GoToAsync calls.
- AppShell.xaml declares implicit routes automatically through ShellContent entries in the flyout/tab structure.
- Routing.RegisterRoute registers explicit routes for pages reached only via GoToAsync, not shown in the flyout.
- Absolute routes (leading "//") reset the navigation stack; relative routes push onto the existing stack.
- [QueryProperty] maps simple query-string values onto bindable page properties automatically.
- IQueryAttributable.ApplyQueryAttributes receives complex objects passed via ShellNavigationQueryParameters without serialization.
- GoToAsync("..") pops one page; Shell.Current.Navigation exposes the stack for direct inspection.
- Route strings are case-sensitive and must be registered before use or GoToAsync throws at runtime.
Practice what you learned
1. What does an absolute route (starting with "//") do when passed to GoToAsync?
2. Which mechanism is used to register a page that exists only for deep linking, not as a flyout/tab item?
3. How does a page receive a complex object (not a simple string) passed through Shell navigation?
4. What happens if GoToAsync is called with a route string that was never registered?
Was this page helpful?
You May Also Like
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics