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.Pushmethod. - Remove the top most page by calling 
INavigationStack.Popmethod 
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.SetResultmethod. 
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 
StackPagecan be updated after it was push onto the stack by callingINavigationStack.SetNameandINavigationStack.SetMenumethods. - 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.



