'How to create NavMenu with collapsible submenu in .Net Core Blazor app

I am trying to create a blazor navmenu which has a shape like this

  • item a

  • item b

when I click on item b it expands with sub menu like this and clicking on subitems, new pages open

  • item a

  • item b

    • subitem 1

    • subitem 2

I just edited the original blazor app but no success. The button appears but it doesn't collapse submenu. any idea?

<div class="@NavMenuCssClass" @onclick="@ToggleNavMenu">
<ul class="nav flex-column">
    <li class="nav-item px-3">
        <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
            <span class="oi oi-home" aria-hidden="true"></span> Home
        </NavLink>
    </li>
    <li class="nav-item px-3">
        <NavLink class="nav-link" href="counter">
            <span class="oi oi-plus" aria-hidden="true"></span> Counter
        </NavLink>
    </li>
    <li class="nav-item px-3">
        <NavLink class="nav-link" href="fetchdata">
            <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch Data
        </NavLink>
    </li>
    <li class="dropdown">

        <button data-toggle="collapse" data-target="#demo">Collapsible</button>

        <div id="demo" class="collapse">
            <ul>
                <li class="nav-item px-3">
                    <NavLink class="nav-link" href="meeting">
                        <span class="oi oi-plus" aria-hidden="true"></span> Meetings
                    </NavLink>
                </li>
                <li class="nav-item px-3">
                    <NavLink class="nav-link" href="conference">
                        <span class="oi oi-list-rich" aria-hidden="true"></span> Conferences
                    </NavLink>
                </li>
                <li class="nav-item px-3">
                    <NavLink class="nav-link" href="event">
                        <span class="oi oi-list-rich" aria-hidden="true"></span> Events
                    </NavLink>
                </li>

            </ul>
        </div>

    </li>
</ul>



Solution 1:[1]

Do not use the data-toggle and data-target for it.

These are used by boostrap.js however you do not want to modify the DOM in that way.

What you do instead is to use an if statement and thus let Blazor take care of the rendering:

    <NavLink class="nav-link" @onclick="()=>expandSubNav = !expandSubNav">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
    </NavLink>
    @if (expandSubNav)
    {
        <NavLink class="expand-menu" href="">
            <span>Sub1</span>
        </NavLink>
        <NavLink class="" href="">
            <span>Sub2</span>
        </NavLink>
    }

And put the expandSubNav field into your code section:

@code {

    private bool expandSubNav;

}

Solution 2:[2]

<div class="nav-link" @onclick="()=>expandSubNav = !expandSubNav">
    <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</div>
@if (expandSubNav)
{
    <NavLink class="expand-menu" href="">
        <span>Sub1</span>
    </NavLink>
    <NavLink class="" href="">
        <span>Sub2</span>
    </NavLink>
}

use div instead of NAVLINK. Navlink reloads the page and reset the expandSubNav.

Solution 3:[3]

My Solution, after problems with not closing on click the submenu:

enter image description here

works on Mobile:

enter image description here

 <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
    <div class="container">
        <a class="navbar-brand" href="">MyPrgramm</a>
        <button class="navbar-toggler" type="button" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <NavLink class="nav-link text-dark" href="" Match="NavLinkMatch.All">
                        <span class="oi oi-home" aria-hidden="true"></span> Home
                    </NavLink>
                </li>
                <li class="nav-item">
                    <NavLink class="nav-link text-dark" href="counter">
                        <span class="oi oi-plus" aria-hidden="true"></span> Menu-II
                    </NavLink>
                </li>
                <li class="nav-item dropdown show">
                    <NavLink class="nav-link dropdown-toggle" @onclick="() => expandSubNavSettings = !expandSubNavSettings" id="navbarDropdown" >
                        <span class="oi oi-list-rich" aria-hidden="true"></span> Menu III
                    </NavLink>
                    @if (expandSubNavSettings)
                    {
                        <li class="dropdown-menu show" aria-labelledby="navbarDropdown" @onclick="() => expandSubNavSettings = !expandSubNavSettings">
                            <li class="nav-item">
                                <NavLink class="nav-link text-dark" href="fetchdata">
                                    <span class="oi oi-fork" aria-hidden="true"></span> Fetch
                                </NavLink>
                            </li>
                            <li class="nav-item">
                                <NavLink class="nav-link text-dark" href="counter">
                                    <span class="oi oi-command" aria-hidden="true"></span> Counter
                                </NavLink>
                            </li>
                            <li class="nav-item">
                                <NavLink class="nav-link text-dark" href="home">
                                    <span class="oi oi-home" aria-hidden="true"></span> Home
                                </NavLink>
                            </li>
                        </li>
                    }
                </li>



            </ul>
        </div>
    </div>
</nav>

@code {

bool collapseNavMenu = true;

private bool expandSubNavSettings;


string baseMenuClass = "navbar-collapse d-sm-inline-flex flex-sm-row-reverse";

string NavMenuCssClass => baseMenuClass + (collapseNavMenu ? " collapse" : "");

void ToggleNavMenu()
{
    if(!expandSubNavSettings)
    {
        collapseNavMenu = !collapseNavMenu;
    }


}

}

Solution 4:[4]

You can try this solution if you have more submenus

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">System</a>
    <button class="navbar-toggler" @onclick="() => ToggleNavMenu(navSubmenu)">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" @onclick="() => ToggleNavMenu(NavSubmenu.None)" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" @onclick="() => TogleSubmenu(NavSubmenu.First)">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Submenu 1
            </NavLink>
        </li>

        @if (navSubmenu == NavSubmenu.First)
        {
            <li class="nav-item px-5">
                <NavLink class="nav-link" @onclick="() => ToggleNavMenu()" href="counter">
                    <span class="oi oi-plus" aria-hidden="true"></span> Counter
                </NavLink>
            </li>
            <li class="nav-item px-5">
                <NavLink class="nav-link" @onclick="() => ToggleNavMenu()" href="fetchdata">
                    <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch Data
                </NavLink>
            </li>
        }

        <li class="nav-item px-3">
            <NavLink class="nav-link" @onclick="() => TogleSubmenu(NavSubmenu.Second)">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Submenu 2
            </NavLink>
        </li>

        @if (navSubmenu == NavSubmenu.Second)
        {
            <li class="nav-item px-5">
                <NavLink class="nav-link" @onclick="() => ToggleNavMenu()" href="counter">
                    <span class="oi oi-plus" aria-hidden="true"></span> Counter
                </NavLink>
            </li>
            <li class="nav-item px-5">
                <NavLink class="nav-link" @onclick="() => ToggleNavMenu()" href="fetchdata">
                    <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch Data
                </NavLink>
            </li>
        }
    </ul>
</div>

@code {
    private enum NavSubmenu
    {
        None,
        First,
        Second
    }

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
    private NavSubmenu navSubmenu =  NavSubmenu.None;
    private bool collapseNavMenu = true;


    private void ToggleNavMenu(NavSubmenu? submenu = null)
    {
        collapseNavMenu = !collapseNavMenu;
        navSubmenu = submenu ?? navSubmenu;
    }

    private void TogleSubmenu(NavSubmenu submenu)
    {
        if (navSubmenu == submenu)
            navSubmenu = NavSubmenu.None;
        else
            navSubmenu = submenu;
    }
}

Solution 5:[5]

(End result) I struggled quite a bit with submenus and ended up using this in my project:

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">Submenu master</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" @onclick="()=>ToggleNavMenu()" href=""     Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>

        <li class="nav-item px-3">
            <NavLink class="nav-link" @onclick="()=>ToggleSubmenu()">
                <span class="oi oi-menu" aria-hidden="true"></span> Menu
            </NavLink>
        </li>
        @if (expandMenu)
        {
            <li class="nav-item px-4" @onclick="ToggleNavMenu">
                <NavLink class="nav-link" href="counter">
                    <span class="oi oi-plus" aria-hidden="true"></span> Counter
                </NavLink>
            </li>
            <li class="nav-item px-4" @onclick="ToggleNavMenu">
                <NavLink class="nav-link" href="fetchdata">
                    <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
                </NavLink>
            </li>
        }
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private bool expandMenu;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
        expandMenu = false;
    }

    private void ToggleSubmenu()
    {
        expandMenu = !expandMenu;
    }
}

Solution 6:[6]

If you would like to have perfect look of sub menu items, here is the example:

<li class="nav-item px-3">
            <NavLink class="nav-link" href="cart" @onclick="()=>expandSubMenu= !expandSubMenu">
                <span class="oi oi-cart" aria-hidden="true"></span> Cart
            </NavLink>
            @if (expandSubMenu)
            {
                <ul class="nav flex-column">
                    <li class="nav-item px-3">
                        <NavLink class="nav-link" href="cart/1">
                            <span class="oi oi-file" aria-hidden="true"></span> Sub-1
                        </NavLink>
                    </li>
                    <li class="nav-item px-3">
                        <NavLink class="nav-link" href="cart/2">
                            <span class="oi oi-bar-chart" aria-hidden="true"></span> Sub-1
                        </NavLink>
                    </li>
                </ul>
            }
        </li>

you also need to add expandSubMenu property to the code block

@code {
    private bool collapseNavMenu = true;
    private bool expandSubMenu;//add

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

Solution 7:[7]

Implement Sub-menu by using Bootstrap

Default template of Blazor generate collapse class inside @media in NavMenu.razor.css which interferes with Bootstrap's submenu collapse so we need to rename it to something else.

/* NavMenu.razor.css */
/* Add */
.sidebar-collapse {
    display: none;
}

/* Modify */
@media (min-width: 641px) {
    ...
    
    .sidebar-collapse { 
        /* Never collapse the sidebar for wide screens */
        display: block;
    }
}

On NavMenu.razor replace the same class name inside code block

/* NavMenu.razor */
private string? NavMenuCssClass => collapseNavMenu ? "sidebar-collapse" : null;

Remove @onclick="ToggleNavMenu" from nav parent div

<div class="@NavMenuCssClass">
   <nav class="flex-column">
      ...
   </nav>
</div>

Add Sub-Menu Items. Classes has-submenu, nav-submenu and nav-subitem are optional but will be used for styling later

<div class="nav-item has-submenu px-3">
   <NavLink class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#userSubMenu"> <!-- <= this id -->
       <span class="oi oi-list-rich" aria-hidden="true"></span> Users
   </NavLink>
    <ul class="nav-submenu collapse list-unstyled" id="userSubMenu"> <!-- <= and this id must match -->
       <li class="nav-subitem">
           <NavLink href="users/add">
               <span class="oi oi-plus" aria-hidden="true"></span> Add
           </NavLink>
       </li>
       <li class="nav-subitem">
           <NavLink href="users/list">
               <span class="oi oi-people" aria-hidden="true"></span> View
           </NavLink>
       </li>
   </ul>
</div>

Bootstrap 4.x

Add js files in index.html

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

Bootstrap 5.x

Add js files in index.html

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>

On NavLink Replace data-toggle with data-bs-toggle and data-target with data-bs-target


Sub-menu should be working now.

Additional Styling

Add these styles to NavMenu.razor.css


/* Custom Styles */
.nav-item ::deep .nav-link i {
    width: 2rem;
    font-size: 1.1rem;
}

::deep .nav-item.has-submenu > a {
    position: relative;
}

    ::deep .nav-item.has-submenu > a:after {
        content: "\e02f";
        position: absolute;
        right: 15px;
        color: white;
        font-family: "Icons";
        font-weight: 400;
        font-size: 13px;
        transform: rotateZ(90deg);
        transform-origin: left;
        transition: transform .5s ease;
    }

    ::deep .nav-item.has-submenu > a.collapsed:after {
        transform: rotateZ(0deg);
    }

.nav-item ::deep .nav-submenu .nav-subitem a {
    padding-left: 3rem;
    text-decoration: none;
}

    .nav-item ::deep .nav-submenu .nav-subitem a i {
        width: 2rem;
        font-size: 1.1rem;
    }

Solution 8:[8]

I took a approach with jQuery and also made the menu responsive :

Sample menu

A complete sample can be downloaded from here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Diego Venâncio
Solution 2 Shahzad Ahmad
Solution 3 Wolfgang Schorge
Solution 4
Solution 5 dinozaver
Solution 6 J S
Solution 7 Uzair Ali
Solution 8 Steef