Building a Custom Header Navigation Menu in WordPress

Extending the Walker_Nav_Menu class to build a highly customizable, mobile-friendly navigation system in your WordPress theme.

Note: This is really just a quick "here's how I got it done" article, but I intend to re-visit this, and write a full tutorial.

I normally start my custom WordPress themes with copy of underscores, dropped into my local dev environment. It's a great starting point, and gets you up & running pretty quickly. Using the standard wp_nav_menu() function, you'll get some pretty familiar markup. For instance:

<div class="menu-menu-1-container">
    <ul id="primary-menu" class="menu">
        <li id="menu-item-50" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-50"><a href="#">1.0</a></li>
        <li id="menu-item-60" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-60"><a href="#">2.0</a>
            <ul class="sub-menu">
                <li id="menu-item-61" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-61"><a href="#">2.1</a></li>
                <li id="menu-item-62" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-62"><a href="#">2.2</a></li>
            </ul>
        </li>
    </ul>
</div>

Wow, that's a whole bucket of IDs and classes. This is part of what makes WordPress so portable, compatible, flexible, etc. That's great. But I needed a 100% custom menu system with my own class structure.

I started with this (slightly old) but extremely helpful article by Luke Mlsna, to accomplish my custom extension of the Walker_Nav_Menu class. Once you get a handle on two very key functions involved in the Walker class: start_lvl and start_el, everything sort of grows out from there.

The Goal

I needed to incorporate my elmenu system, complete with its top-to-bottom BEM class structure. I modified Luke's code to remove his class structure, and include a few conditionals to help me inject my classes in the correct positions.

I also needed to control some styles on my mobile nav toggles for pages that have children (and child pages that have grandchildren). To accomplish this I added a pretty basic function to help me look for child menus, and called it in a multi-level conditional.

function hasChildren( $pid ) {
    $children = get_pages( "child_of={$pid}" );
    return ( $children );
}

Other than that, my changes were pretty minimal.

I originally used a custom wp_get_nav_menu_items() function to accomplish this. But, the more I dug into it, the more negative comments I found on the particular method. I thought it best to stick to the Walker solution, since it's a fundamental aspect of dealing with the data structure in WordPress.

The Solution

Add the custom Walker_Nav_Menu class in your theme. ( View mine )

Set it up in functions.php, for instance:

require get_template_directory() . '/inc/ava-nav.php';

Call the wp_nav_menu in your template, with any specific arguments you need (there are plenty of options):

<?php
$args = array(
    'theme_location'  => 'menu-1',
    'menu_id'         => 'primary-menu',
    'menu_class'      => 'nav--main elm__list',
    'container'       => '',
    'container_id'    => '',
    'container_class' => '',
    'walker'          => new Ava_Nav_Menu(),
    'depth'           => 0
);
wp_nav_menu( $args );
?>

Again, this is the quick-and-dirty, and assumes you just need a few hints, and can run with your own solution. The elmenu navigation system I'm using also requires a small bit of jQuery, and a complete set of BEM styles, found in my Sass kit. You can find all of this in my project repo, if you feel like checking it out. But you certainly don't need all of that to use this idea. Just being able to output some semantic markup with your own classes, using the wp_nav_menu will get you pretty far.