Blazor.NavigationStack

Blazor Navigation Stack

NuGet (with prereleases)

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! ⭐

Table of Contents

Samples/Demo

Live Demo

Multi-step forms Visual customization
Demonstration of nested workflow navigation. Demonstration of a visually customized navigation stack.
Wizards Preserving states
Demonstration of a multi-step wizard. Demonstration of state preservation across navigation.

Installation

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.

Basic Usage

Obtaining INavigationStack

All operations on the Blazor navigation stack can be done through INavigationStack interface. INavigationStack interface can be obtained through many ways.

  1. Through context
     <NavigationStack>  
      <BaseContent>  
      <button @onclick="()=>StartClicked(context)">Start</button>  
      </BaseContent>  
     </NavigationStack>  
    	  
     @code {  
       private void StartClicked(INavigationStack stack) {  
             //push pages to the navigation stack  
       }  
     }
    
  2. Through cascading parameter
ComponentWithNavigationStack.razor
``` razor
<NavigationStack>  
 <BaseContent>  
 <StackPageComponent/>  
 </BaseContent>  
</NavigationStack>
```
StackPageComponent.razor
``` razor
@code {  
  [CascadingParameter]  
    public INavigationStack? NavigationStack { get; set; }  
}
```
  1. Through @ref
     <NavigationStack @ref="_stack">  
     </NavigationStack>  
    	  
     @code {  
       private INavigationStack? _stack;  
     }
    

    Add a page to the 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 ``` razor 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(),
});

### Add a page expecting a result
+ 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
``` 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);  
    }  
}

Preserving states

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;
    }

Interacting with a page on top of the stack

// Set a custom menu to the curret page NavigationStack?.SetMenu(CustomMenuFragment);

// Rerender the navigation stack including the current page NavigationStack?.Refresh();


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

```cshtml
<NavigationStack 
    BaseName="Home"
    Layout="@CustomLayout"
    HeaderStack="@CustomHeaderStack"
    Header="@CustomHeader"
    HeaderSeparator="@CustomHeaderSeparator"
    Back="@CustomBack">
    <BaseContent>
        <!-- Your base content here -->
    </BaseContent>
</NavigationStack>

Overall Layout

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:

private RenderFragment<NavigationStack.HeaderContext> CustomHeader => context => {
    return @<div class="@(context.IsActive ? "header-active" : "header-inactive")">
        @context.Name
    </div>;
};

The HeaderContext provides:

Header Stack

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:

private RenderFragment CustomHeaderSeparator => @<div class="separator-arrow">
    <span>→</span>
</div>;

The MenuContext provides:

Back Button

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:

For a complete example, see the Blazor.NavigationStack.TestApp/Components/Pages/Customize.razor component which demonstrates a dark-themed custom navigation stack.