Install
$ agentstack add skill-dotnet-skills-maui-shell-navigation ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
.NET MAUI Shell Navigation
Implement page navigation in .NET MAUI apps using Shell. Shell provides URI-based navigation, a flyout menu, tab bars, and a four-level visual hierarchy — all configured declaratively in XAML.
When to Use
- Setting up top-level app navigation with tabs or a flyout menu
- Navigating between pages programmatically with
GoToAsync - Passing data between pages via query parameters or object parameters
- Registering detail-page routes for push navigation
- Guarding navigation with confirmation dialogs (e.g., unsaved changes)
- Customizing back button behavior per page
When Not to Use
- Deep linking from external URLs or app links — see .NET MAUI deep linking docs
- Data binding on navigation target pages — use
maui-data-binding - Dependency injection for pages and view models — use
maui-dependency-injection - Apps using
NavigationPagewithout Shell (different navigation API)
Inputs
- A .NET MAUI project with
AppShell.xamlas the root shell - Pages (
ContentPage) to navigate between - Route names for detail pages not in the visual hierarchy
Shell Visual Hierarchy
Shell uses a four-level hierarchy. Each level wraps the one below it:
Shell
├── FlyoutItem / TabBar (top-level grouping)
│ ├── Tab (bottom-tab grouping)
│ │ ├── ShellContent (page slot → ContentPage)
│ │ └── ShellContent (multiple = top tabs)
│ └── Tab
└── FlyoutItem / TabBar
- FlyoutItem — appears in the flyout menu; contains
Tabchildren - TabBar — bottom tab bar with no flyout entry
- Tab — groups
ShellContent; multiple children produce top tabs - ShellContent — each points to 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 |
Workflow: Set Up AppShell
- Define
AppShell.xamlinheriting fromShell - Add
FlyoutItemorTabBarelements for top-level navigation - Add
Tabelements for bottom tabs; nest multipleShellContentfor top tabs - Always use
ContentTemplatewithDataTemplateso pages load on demand - Register detail-page routes in the
AppShellconstructor
// AppShell.xaml.cs
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute("animaldetails", typeof(AnimalDetailsPage));
Routing.RegisterRoute("editanimal", typeof(EditAnimalPage));
}
}
Workflow: Navigate with GoToAsync
All programmatic navigation uses Shell.Current.GoToAsync. Always await the call.
Route Prefixes
| Prefix | Meaning | |--------|---------------------------------------------| | // | Absolute route from Shell root | | (none) | Relative; pushes onto the current nav stack | | .. | Go back one level | | ../ | Go back then navigate forward |
Navigation Examples
// 1. Absolute — switch to a specific hierarchy location
await Shell.Current.GoToAsync("//animals/cats/domestic");
// 2. Relative — push a registered detail page
await Shell.Current.GoToAsync("animaldetails");
// 3. With query string parameters
await Shell.Current.GoToAsync($"animaldetails?id={animal.Id}");
// 4. Go back one page
await Shell.Current.GoToAsync("..");
// 5. Go back two pages
await Shell.Current.GoToAsync("../..");
// 6. Go back one page, then push a different page
await Shell.Current.GoToAsync("../editanimal");
Workflow: Pass Data Between Pages
Option 1: IQueryAttributable (Preferred)
Implement on ViewModels to receive all parameters in one call:
public class AnimalDetailsViewModel : ObservableObject, IQueryAttributable
{
public void ApplyQueryAttributes(IDictionary query)
{
if (query.TryGetValue("id", out var id))
AnimalId = id.ToString();
}
}
Option 2: QueryProperty Attribute
Apply directly on the page class:
[QueryProperty(nameof(AnimalId), "id")]
public partial class AnimalDetailsPage : ContentPage
{
public string AnimalId { get; set; }
}
Option 3: Complex Objects via ShellNavigationQueryParameters
Pass objects without serializing to strings:
var parameters = new ShellNavigationQueryParameters
{
{ "animal", selectedAnimal }
};
await Shell.Current.GoToAsync("animaldetails", parameters);
Receive via IQueryAttributable:
public void ApplyQueryAttributes(IDictionary query)
{
Animal = query["animal"] as Animal;
}
Workflow: Guard Navigation
Use GetDeferral() in OnNavigating for async checks (e.g., "save unsaved changes?"):
// In AppShell.xaml.cs
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();
}
}
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:
Tab Bar Appearance
| Attached Property | Type | Purpose | |--------------------------------|---------|--------------------------------| | Shell.TabBarBackgroundColor | Color | Tab bar background | | Shell.TabBarForegroundColor | Color | Selected icon color | | Shell.TabBarTitleColor | Color | Selected tab title color | | Shell.TabBarUnselectedColor | Color | Unselected tab icon/title | | Shell.TabBarIsVisible | bool | Show/hide the tab bar |
Flyout Configuration
FlyoutBehavior
Set on Shell: Disabled, Flyout, or Locked.
...
FlyoutDisplayOptions
Controls how children appear in the flyout:
AsSingleItem(default) — one flyout entry for the groupAsMultipleItems— each childTabgets its own entry
MenuItem (Non-Navigation Flyout Entries)
Back Button Behavior
Customize the back button per page:
Properties: Command, CommandParameter, IconOverride, TextOverride, IsVisible, IsEnabled.
Inspecting Navigation State
// Current URI location
string location = Shell.Current.CurrentState.Location.ToString();
// Current page
Page page = Shell.Current.CurrentPage;
// Navigation stack of the current tab
IReadOnlyList stack = Shell.Current.Navigation.NavigationStack;
Navigation Events
Override in AppShell:
protected override void OnNavigated(ShellNavigatedEventArgs args)
{
base.OnNavigated(args);
// args.Current, args.Previous, args.Source
}
ShellNavigationSource values: Push, Pop, PopToRoot, Insert, Remove, ShellItemChanged, ShellSectionChanged, ShellContentChanged, Unknown.
Common Pitfalls
- Eager page creation: Using
Contentdirectly instead ofContentTemplatewithDataTemplatecreates all pages at Shell init, hurting startup time. Always useContentTemplate. - Duplicate route names:
Routing.RegisterRoutethrowsArgumentExceptionif a route name matches an existing route or a visual hierarchy route. Every route must be unique across the app. - Relative routes without registration: You cannot
GoToAsync("somepage")unlesssomepagewas registered withRouting.RegisterRoute. Visual hierarchy pages use absolute//routes. - Fire-and-forget GoToAsync: Not awaiting
GoToAsynccauses race conditions and silent failures. Alwaysawaitthe call. - Wrong absolute route path: Absolute routes must match the full path through the visual hierarchy (
//FlyoutItem/Tab/ShellContent). Wrong paths produce silent no-ops, not exceptions. - Manipulating Tab.Stack directly: The navigation stack is read-only. Use
GoToAsyncfor all navigation changes. - Forgetting
GetDeferral()for async guards: Synchronous cancellation inOnNavigatingworks, but async checks requireGetDeferral()/deferral.Complete()to avoid race conditions.
References
references/shell-navigation-api.md— Full API reference for Shell hierarchy, routes, tabs, flyout, and navigation- .NET MAUI Shell Navigation
- .NET MAUI Shell Tabs
- .NET MAUI Shell Flyout
- .NET MAUI Shell Pages
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: dotnet
- Source: dotnet/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.