Building a User-Friendly Menu in WordPress Without Relying on NavWalker
José Debuchy
WordPress
How many times have we hit our heads against the wall trying to edit a WordPress NavWalker? This class, based on the abstract Walker class, is impractical, and editing menus exactly the way you want is usually a complicated task. To avoid this, we are going to use a Composer package that allows much greater control over navigation and lets us adapt it exactly to our needs:
https://github.com/Log1x/navi/
This package works perfectly with Sage 10.
Installation
Inside the theme folder, run
composer require log1x/navi
Usage with Sage 10
Menu Setup
We start on the WordPress side. In the app folder we have the setup.php file where, by default, the Primary Navigation menu is registered. Other menus can also be added (as in the example) if needed.
/**
* Register navigation menus
* @link <https://developer.wordpress.org/reference/functions/register_nav_menus/>
*/
register_nav_menus([
'primary_navigation' => __('Primary Navigation', 'sage'),
'footer_menu' => __('Footer Menu', 'sage'),
]);
Menu Creation
We go to the WordPress dashboard and, inside Appearance/Menus, we create a new menu. By default, WordPress already includes one.

Location Assignment
We add all the items we consider necessary to the menu and save it. Once the menu is saved, we choose the location we want to assign to it, based on the registrations created in step 1. In this case, under Display location, we choose the Primary Navigation option.
The Menu Name shown in the WordPress panel has nothing to do with the name we registered in step 1. However, we usually keep a 1-to-1 relationship, so it is healthy for them to have the same or a similar name.

Creating a View Composer using Acorn
Once we have created the menu we want to display, we move to the code. Using the Acorn CLI extension, we create a View Composer called Navigation.
# site/web/app/themes/sage
wp acorn make:composer Navigation
Variable Definition and Target Views
In our case, we are going to expose the $navigation variable (which will be generated thanks to the Navi::build('primary_navigation')->toArray() method provided by the package) to the partials.navigation view.
The View Composer will be located at theme-name/app/View/Composers/Navigation.php
<?php
namespace App\\View\\Composers;
use Log1x\\Navi\\Facades\\Navi;
use Roots\\Acorn\\View\\Composer;
class Navigation extends Composer
{
/**
* List of views served by this composer.
*
* @var array
*/
protected static $views = [
'partials.navigation',
];
/**
* Data to be passed to view before rendering.
*
* @return array
*/
public function with()
{
return [
'navigation' => $this->navigation(),
];
}
/**
* Returns the primary navigation.
*
* @return array
*/
public function navigation()
{
if (Navi::build('primary_navigation')->isEmpty()) {
return;
}
return Navi::build('primary_navigation')->toArray();
}
}
Editing the Blade Partial
After registering the $navigation variable, we can now edit the Blade file however we want. We create the navigation.blade.php file inside theme-name/resources/views/partials/ and customize the menu as needed.
We can take some examples from the Bootstrap documentation. In our case, we will use one of the navbar examples.
@if ($navigation)
<div class="collapse navbar-collapse" id="main-navbar">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
@foreach ($navigation as $item)
<li class="nav-item{{ $item->children ? ' dropdown' : '' }}">
@if (!$item->children)
<a class="nav-link {{ $item->classes ?? '' }} {{ $item->active ? ' active' : '' }}" href="{{ $item->url }}">{!! $item->label !!}</a>
@else
<a class="nav-link dropdown-toggle" href="#" id="dropdown-{{ $loop->index }}" data-bs-toggle="dropdown" aria-expanded="false">{!! $item->label !!}</a>
<ul class="dropdown-menu" aria-labelledby="dropdown-{{ $loop->index }}">
@foreach ($item->children as $child)
<li>
<a class="dropdown-item {{ $child->classes ?? '' }} {{ $child->active ? 'active' : '' }}" href="{!! $child->url !!}">{!! $child->label !!}</a>
</li>
@endforeach
</ul>
@endif
</li>
@endforeach
</ul>
<form>
<input class="form-control" type="text" placeholder="Search" aria-label="Search">
</form>
</div>
@endif
Including the Partial
Finally, in the header partial theme-name/resources/views/partials/header.blade.php, we include this navigation partial using the @include directive. This allows us to keep the code clean and reusable, and it can be used in other parts of the site. This is a simple example that can be adapted to the needs of each project.
<header class="banner">
<div class="container">
<div class="d-flex justify-content-lg-between align-items-center">
<a class="brand" href="{{ home_url('/') }}">
<img src="@option('logo', 'url')" alt="@option('logo', 'alt')">
</a>
<nav class="nav-primary align-items-lg-center" id="primary-navigation" tabindex="-1">
@include('partials.navigation')
</nav>
</div>
</div>
</header>
Final Result
Here is the final result of the menu. In this case we do not show it, but if there were a single-level dropdown, it would be displayed automatically, just as Bootstrap does.

If you have more advanced requirements, the package documentation shows the response you will get from the $navigation variable.
Feb 12, 2026
WordPress
Digital Experience Optimization Guide for Enterprise Teams
Apr 1, 2026
WordPress
Top 6 Best Marketing Automation Platforms 2026
Mar 9, 2026
WordPress