.NET MAUI Navigation: Shell, Tabs, Modals Explained

Navigation in .NET MAUI: Your Complete Guide to Getting Around
This post is part 4 of 5 in the series Mastering .NET MAUI: From Beginner to Pro

Did you know that a poorly implemented navigation system is the #1 reason users abandon apps? That stat got my attention years ago, and ever since, I’ve made navigation a first-class citizen in every .NET MAUI project I touch. If you’ve ever struggled with making your app’s navigation feel smooth and intuitive, or wondered about passing data cleanly between pages, this guide is for you.

Let’s break down everything you need to know about navigation in .NET MAUI: from Shell to deep linking, and all the events in between.

Types of Navigation in .NET MAUI

.NET MAUI offers a few primary navigation models depending on how you structure your app.

Shell Navigation

Shell is the modern and recommended approach for .NET MAUI apps.

await Shell.Current.GoToAsync("//mainpage/details");

This command navigates to the Details page under the MainPage route. The // prefix resets the navigation stack, ensuring clean forward-only navigation.

Why use Shell?

  • URL-based routing
  • Built-in flyout and tab support
  • Declarative navigation structure

Flyout, Tabbed, and Stack Navigation

If you’re not using Shell, you can fall back to the traditional page-based navigation.

Flyout Page:

MainPage = new FlyoutPage
{
    Flyout = new MenuPage(),
    Detail = new NavigationPage(new HomePage())
};

Tabbed Page:

MainPage = new TabbedPage
{
    Children = { new HomePage(), new SettingsPage() }
};

Stack Navigation:

await Navigation.PushAsync(new DetailsPage());

This is useful when you want a back-stack experience like a browser.

Passing Data Between Pages

There are two common patterns: query parameters (Shell) and constructor/property injection (non-Shell).

Using Query Parameters

Shell makes it easy to pass parameters via the URL:

await Shell.Current.GoToAsync($"details?id={item.Id}&name={item.Name}");

In your target page:

[QueryProperty("Id", "id")]
[QueryProperty("Name", "name")]
public partial class DetailsPage : ContentPage
{
    public string Id { get; set; }
    public string Name { get; set; }
}

Explanation: Decorate properties with QueryProperty, matching them to the URL parameter name.

Navigating with Parameters in Shell

If you’re navigating with complex objects, serialize them to JSON:

string json = JsonSerializer.Serialize(myObject);
string encoded = Uri.EscapeDataString(json);
await Shell.Current.GoToAsync($"details?data={encoded}");

And in the destination page:

[QueryProperty("Data", "data")]
public partial class DetailsPage : ContentPage
{
    public string Data { get; set; }

    public MyModel Deserialized => JsonSerializer.Deserialize<MyModel>(Uri.UnescapeDataString(Data));
}

Deep Linking and Modal Navigation

Implementing Deep Linking

Shell supports deep links via URI handlers:

Routing.RegisterRoute("profile", typeof(ProfilePage));

Now, external links like myapp://profile can open the page.

Working with Modal Pages

For modals (dialog-style full screens), use:

await Navigation.PushModalAsync(new ModalPage());

To close it:

await Navigation.PopModalAsync();

Tip: Modal pages do not integrate with the Shell navigation stack. They live in a separate stack.

4. Navigation Lifecycle Events

Knowing when a page appears or disappears helps manage resources and state.

Handling Appearing and Disappearing Events

protected override void OnAppearing()
{
    base.OnAppearing();
    Debug.WriteLine("Page is now visible");
}

protected override void OnDisappearing()
{
    base.OnDisappearing();
    Debug.WriteLine("Page is now hidden");
}

Managing Page States

For more complex scenarios (like pausing/resuming media or tracking analytics):

  • Use these lifecycle methods
  • Combine them with ViewModel logic to persist state

FAQ: Common Questions About .NET MAUI Navigation

Can I mix Shell and non-Shell navigation?

It’s possible but not recommended. Shell manages its own stack, and mixing patterns can lead to inconsistent states.

How do I test navigation logic?

Abstract navigation services behind an interface (e.g., INavigationService) and mock them during tests.

What’s the difference between PushAsync and GoToAsync?

PushAsync is stack-based (traditional), while GoToAsync is route-based (Shell).

Conclusion: Navigate Like a Pro in .NET MAUI

Mastering navigation is about more than moving between screens—it’s about creating fluid, intuitive journeys for your users. Whether you’re building a single-page experience or a tab-heavy monster, the tools in .NET MAUI have you covered.

Now, it’s your turn—revamp your navigation setup and watch your app’s UX go from “meh” to magic.

Still got questions? Drop a comment or check out the rest of the .NET MAUI deep dives on the blog.

Leave a Reply

Your email address will not be published. Required fields are marked *