Claude
Skills
Sign in
Back

maui-shell-navigation

Included with Lifetime
$97 forever

Guide for implementing Shell-based navigation in .NET MAUI apps. Covers AppShell setup, visual hierarchy (FlyoutItem, TabBar, Tab, ShellContent), URI-based navigation with GoToAsync, route registration, query parameters, back navigation, flyout and tab configuration, navigation events, and navigation guards. Use when: setting up Shell navigation, adding tabs or flyout menus, navigating between pages with GoToAsync, passing parameters between pages, registering routes, customizing back button behavior, or guarding navigation with confirmation dialogs. Do not use for: deep linking from external URLs (see .NET MAUI deep linking documentation), data binding on pages (use maui-data-binding), dependency injection setup (use maui-dependency-injection), or NavigationPage-only apps that don't use Shell.

General

What this skill does


# .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](https://learn.microsoft.com/dotnet/maui/fundamentals/app-links)
- Data binding on navigation target pages — use `maui-data-binding`
- Dependency injection for pages and view models — use `maui-dependency-injection`
- Apps using `NavigationPage` without Shell (different navigation API)

## Inputs

- A .NET MAUI project with `AppShell.xaml` as 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 `Tab` children
- **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

1. Define `AppShell.xaml` inheriting from `Shell`
2. Add `FlyoutItem` or `TabBar` elements for top-level navigation
3. Add `Tab` elements for bottom tabs; nest multiple `ShellContent` for top tabs
4. **Always use `ContentTemplate`** with `DataTemplate` so pages load on demand
5. Register detail-page routes in the `AppShell` constructor

```xml
<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>
```

```csharp
// 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

```csharp
// 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:

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

### Option 2: QueryProperty Attribute

Apply directly on the page class:

```csharp
[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:

```csharp
var parameters = new ShellNavigationQueryParameters
{
    { "animal", selectedAnimal }
};
await Shell.Current.GoToAsync("animaldetails", parameters);
```

Receive via `IQueryAttributable`:

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

## Workflow: Guard Navigation

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

```csharp
// 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:

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

### 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          |

```xml
<!-- Hide the tab bar on a specific page -->
<ContentPage Shell.TabBarIsVisible="False" ... />
```

## Flyout Configuration

### FlyoutBehavior

Set on `Shell`: `Disabled`, `Flyout`, or `Locked`.

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

### FlyoutDisplayOptions

Controls how children appear in the flyout:

- `AsSingleItem` (default) — one flyout en

Related in General