A flexible navigation stack for Blazor. Manage complex navigation while retaining states, ideal for wizards, multi-step forms, and nested workflows.
If you find this project helpful, please consider giving it a star! ⭐
- Samples/Demo
- Installation
- Basic Usage
- Preserving states
- Interacting with a page on top of the stack
- Customization
Multi-step forms | Visual customization |
![]() |
![]() |
Wizards | Preserving states |
![]() |
![]() |
Add the nuget package in your Blazor project
> dotnet add package Blazor.NavigationStack
OR
PM> Install-Package Blazor.NavigationStack
Nuget package page can be found here.
All operations on the Blazor navigation stack can be done through INavigationStack
interface.
INavigationStack
interface can be obtained through many ways.
-
Through context
<NavigationStack> <BaseContent> <button @onclick="()=>StartClicked(context)">Start</button> </BaseContent> </NavigationStack> @code { private void StartClicked(INavigationStack stack) { //push pages to the navigation stack } }
-
Through cascading parameter
ComponentWithNavigationStack.razor
<NavigationStack> <BaseContent> <StackPageComponent/> </BaseContent> </NavigationStack>
StackPageComponent.razor
@code { [CascadingParameter] public INavigationStack? NavigationStack { get; set; } }
-
Through @ref
<NavigationStack @ref="_stack"> </NavigationStack> @code { private INavigationStack? _stack; }
- Adding a page on top of the current one by calling
INavigationStack.Push
method. - Remove the top most page by calling
INavigationStack.Pop
method
void ReturnClicked() {
_stack.Pop();
}
RenderFragment Content() {
return @<div>
Content of the page
<button @onclick="ReturnClicked">Return</button>
</div>;
}
await _stack.Push(new StackPage() {
Content = Content(),
});
- Adding a page on top of the current one by calling
INavigationStack.Push<T>
method. - Setting a result and pop the cuurent page by calling
INavigationStack.SetResult
method.
ComponentWithStack.razor
RenderFragment Content() {
return @<StackPageComponent/>;
}
NavigationStack.Result<string> result = await _stack.Push<string>(new StackPage() {
Content = Content(),
});
if(result.IsCanceled) return;
string? valueFromStackPage = result.Value;
StackPageComponent.razor
<div>
<input type="text" @bind="_value"/>
<button @onclick="OkClicked">Ok</button>
</div>
@code {
[CascadingParameter]
public INavigationStack? NavigationStack { get; set; }
private string _value = "";
private void OkClicked() {
NavigationStack?.SetResult(_value);
}
}
State of components can be preserved in the stack frame itself.
private class Data {
public required string Value1 { get; init; }
public required string Value2 { get; init; }
}
private Data? _data;
private async Task ShowTableBuilder(INavigationStack stack) {
string? value1 = null;
string? value2 = null;
async Task SelectValue1() {
value1 = await ShowSelectValue(stack);
}
async Task SelectValue2() {
value2 = await ShowSelectValue(stack);
}
void OkClicked() {
if (value1 == null || value2 == null) return;
stack.SetResult(new Data() {
Value1 = value1,
Value2 = value2,
});
}
RenderFragment Content() {
return @<div>
<p>Select two values by navigating to sub-pages.
The selected values will be preserved in the stack frame allow
each value to be selected separately.</p>
<table>
<tr>
<th colspan="3">Select values</th>
</tr>
<tr>
<td><strong>Value1</strong></td>
<td>@value1</td>
<td>
<button @onclick="SelectValue1">Select value</button>
</td>
</tr>
<tr>
<td><strong>Value2</strong></td>
<td>@value2</td>
<td>
<button @onclick="SelectValue2">Select value</button>
</td>
</tr>
<tr>
<td colspan="3">
<button disabled="@(value1 == null || value2 == null)" @onclick="OkClicked">Ok</button>
</td>
</tr>
</table>
</div>;
}
NavigationStack.Result<Data> result = await stack.Push<Data>(new StackPage() {
Content = Content(),
Name = "Table Builder"
});
_data = result.Value;
}
private async Task<string?> ShowSelectValue(INavigationStack stack) {
string? result = null;
void OkClicked() {
stack.Pop();
}
RenderFragment Content() {
return @<div>
<p>Enter a value in the input field and click 'Ok' to return to the 'Table Builder'.</p>
<input type="text" @bind="result"/>
<button @onclick="OkClicked">Ok</button>
</div>;
}
bool success = await stack.Push(new StackPage() {
Content = Content(),
Name = "Select Value",
});
if (!success) return null;
return result;
}
- Properties of the
StackPage
can be updated after it was push onto the stack by callingINavigationStack.SetName
andINavigationStack.SetMenu
methods. - Changes to the content of the current page can be updated by calling
INavigationStack.Refresh
. It will rerender the page similar to calling StateHasChanged on a component.
// Change the name of the current page
NavigationStack?.SetName("Page Title");
// Set a custom menu to the curret page
NavigationStack?.SetMenu(CustomMenuFragment);
// Rerender the navigation stack including the current page
NavigationStack?.Refresh();
You can customize virtually every part of the Navigation Stack, including:
- Overall layout
- Header stack appearance
- Individual header styling
- Header separators
- Menu appearance
- Back button
To customize the Navigation Stack, pass custom RenderFragments to the appropriate parameters of the NavigationStack component:
<NavigationStack
BaseName="Home"
Layout="@CustomLayout"
HeaderStack="@CustomHeaderStack"
Header="@CustomHeader"
HeaderSeparator="@CustomHeaderSeparator"
Back="@CustomBack">
<BaseContent>
<!-- Your base content here -->
</BaseContent>
</NavigationStack>
The layout controls the overall structure of the navigation stack:
private RenderFragment<NavigationStack.LayoutContext> CustomLayout => context => {
return @<div class="dark-layout">
<div class="dark-header">
<div class="header-left">
@context.BackButton
@context.HeaderStack
</div>
<div class="header-right">
@context.Menu
</div>
</div>
<div class="dark-content">
@context.Content
</div>
</div>;
};
The LayoutContext
provides:
BackButton
: Back navigation buttonHeaderStack
: Breadcrumbs/header navigationMenu
: Menu for the current pageContent
: The main content area
Customize how navigation headers appear:
private RenderFragment<NavigationStack.HeaderContext> CustomHeader => context => {
return @<div class="@(context.IsActive ? "header-active" : "header-inactive")">
@context.Name
</div>;
};
The HeaderContext
provides:
Name
: Title of the pageIsActive
: Whether this is the currently active page
Change how the entire breadcrumb/header navigation appears:
private RenderFragment<NavigationStack.HeaderStackContext> CustomHeaderStack => context => {
return @<div class="header-stack">
@{
RenderFragment header = context.Headers.First();
<div class="header-item">
@header
</div>
}
</div>;
};
The HeaderStackContext
provides:
Headers
: Collection of header fragments to display
Customize the separator between headers:
private RenderFragment CustomHeaderSeparator => @<div class="separator-arrow">
<span>→</span>
</div>;
The MenuContext
provides:
Options
: Collection of menu item fragments to display
Change the appearance and behavior of the back button:
private RenderFragment<NavigationStack.BackContext> CustomBack => context => {
return @<button class="back-button" @onclick="context.Back">
<span>◀</span> Back
</button>;
};
The BackContext
provides:
Back
: Action to performINavigationStack.Cancel
For a complete example, see the Blazor.NavigationStack.TestApp/Components/Pages/Customize.razor
component which demonstrates a dark-themed custom navigation stack.