search ]

Create a Clean Navigation Menu Markup in WordPress

By default, most WordPress themes come with a menu in the traditional location, either at the top of the page or in the sidebar. In most cases, the styling of those menus also follows tradition and is quite standard.

However, many of you may find the same design and standard behavior not aligned with your desires and requirements. Therefore, there is a high demand for custom-adapted menus in WordPress themes.

In this guide, I will show you how to create and add menus in WordPress themes, but I won’t extensively discuss the menu creation and adjustments. Instead, I will focus mainly on creating a menu with clean markup, making it easier to style later and saving some unnecessary code on our website.

Creating a Menu in WordPress

The first step in creating a customized menu in WordPress is to register the menu using the register_nav_menu function. To do this, add the following code to the functions.php file in your child theme:

function register_my_menu() {
    register_nav_menu( 'main-menu', __( 'Main Menu' ) );
}
add_action( 'after_setup_theme', 'register_my_menu' );

The function comes with several parameters that you can find in the WordPress Codex. Here are the default options for this function:

$args = array(
    'theme_location'  => '',
    'menu'            => '',
    'container'       => 'div',
    'container_class' => '',
    'container_id'    => '',
    'menu_class'      => 'menu',
    'menu_id'         => '',
    'echo'            => true,
    'fallback_cb'     => 'wp_page_menu',
    'before'          => '',
    'after'           => '',
    'link_before'     => '',
    'link_after'      => '',
    'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
    'depth' => 0,
    'walker' => ''
);

In practice, you rarely need to customize all of these parameters. I won’t delve into explaining them beyond the scope of this guide. However, pay attention to the first and crucial parameter, theme_location. This parameter determines which menu to display based on the slug you set when registering the menu using the register_nav_menu() function.

Displaying the Menu in a WordPress Template

To display the menu, we will use the wp_nav_menu function. Add the following code wherever you want the menu to appear in your child theme template:

wp_nav_menu( array(
    'theme_location' => 'main-menu'
) );

Check out the new menu you’ve created – it’s simple and nice. However, if you take a look at the markup that the wp_nav_menu() function generates, you’ll find something like this:


<div class="menu-test-container">
    <ul id="menu-test" class="menu">
        <li id="menu-item-6"
            class="menu-item menu-item-type-custom menu-item-object-custom current-menu-item current_page_item menu-item-home menu-item-6">
            <a href="http://example.com/">Home</a></li>
        <li id="menu-item-7" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-7"><a
                href="http://example.com/demos/">Demos</a></li>
        <li id="menu-item-8" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-8"><a
                href="http://example.com/downloads/">Downloads</a></li>
        <li id="menu-item-9" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-9"><a
                href="http://example.com/docs/">Documentation</a></li>
    </ul>
</div>

You may find this mess of classes and IDs quite frustrating. No matter what you do or which settings of the function you tweak, you won’t be able to achieve a clean and elegant menu markup.

Creating a Menu with Clean Markup Using Walker Class

If you dig into the WordPress core code, you’ll discover that the wp_nav_menu() function uses the built-in Walker_Nav_Menu class to render and print the menu. Walker_Nav_Menu is a php class that, unfortunately (in this case), initially adds those IDs and classes to each item in your menu and uses several filters to do so.

To display a menu with clean and organized markup, you can create your own Walker Class that extends Walker_Nav_Menu and overrides its default behavior, thus avoiding the use of those filters. To do this, create a php class in your template that “extends” Walker_Nav_Menu, like the following example:

class My_Walker_Nav_Menu extends Walker_Nav_Menu {
	public function start_lvl( &$output, $depth = 0, $args = array() ) {
		$output .= '<ul>';
	}

	public function end_lvl( &$output, $depth = 0, $args = array() ) {
		$output .= '</ul>';
	}

	public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
		$classes = array();
		if( !empty( $item->classes ) ) {
			$classes = (array) $item->classes;
		}

		$active_class = '';
		if( in_array('current-menu-item', $classes) ) {
			$active_class = ' class="active"';
		} else if( in_array('current-menu-parent', $classes) ) {
			$active_class = ' class="active-parent"';
		} else if( in_array('current-menu-ancestor', $classes) ) {
			$active_class = ' class="active-ancestor"';
		}

		$url = '';
		if( !empty( $item->url ) ) {
			$url = $item->url;
		}

		$output .= '<li'. $active_class . '><a href="' . $url . '">' . $item->title . '</a></li>';
	}

	public function end_el( &$output, $item, $depth = 0, $args = array() ) {
		$output .= '</li>';
	}
}

Now we have a Walker that prints minimal markup. To use it, simply add an argument to the wp_nav_menu() function in your template, like this example:

wp_nav_menu(array(
    // Whatever you used to register the nav menu with
    'theme_location' => 'main-menu',
    // Instantiate our class & pass it as an argument
    'walker' => new My_Walker_Nav_Menu(),
));

In general, you can stop here. However, if you want to clean up the markup even more, you can add two more arguments when calling wp_nav_menu():

wp_nav_menu(array(
    'theme_location' => 'main-menu',
    'walker' => new My_Walker_Nav_Menu(),
    'container' => false,
    'items_wrap' => '<nav id="%1$s"><ul>%3$s</ul></nav>'
));

Notice how our menu’s markup looks now – clean and organized, just the way we like it:

<nav>
    <ul>
        <li><a class="active-parent" href="http://example.com/">Home</a></li>
        <li><a href="http://example.com/demos/">Demos</a></li>
        <li><a href="http://example.com/downloads/">Downloads</a></li>
        <li><a href="http://example.com/docs/">Documentation</a></li>
    </ul>
</nav>

Pay attention to the active-parent class added as an indication of the active page. You can use it to style this menu item as desired.

Alternative Option for Cleaning Menu Markup

There are indeed more options to display clean markup when using wp_nav_menu(), and one of them involves using filters. Simply add the following code to the functions.php file in your child theme:

function wp_nav_menu_filter( $var ) {
    return is_array( $var ) ? array_intersect( $var, array( 'current-menu-item' ) ) : '';
}

add_filter( 'nav_menu_css_class', 'wp_nav_menu_filter', 100, 1 );
add_filter( 'nav_menu_item_id', 'wp_nav_menu_filter', 100, 1 );
add_filter( 'page_css_class', 'wp_nav_menu_filter', 100, 1 );

It might seem very simple, and you might wonder why we used the Walker Class at all if it can be achieved using filters. The reason, or more accurately, the limitation of using filters is that you won’t receive a specific class and indication for the currently active menu item, as we received the active-parent class earlier.

In Conclusion

In this guide, we briefly saw how to build a customized menu in WordPress themes and explained how to use the Walker Class to display the menu’s markup in a cleaner way. I’ll leave it up to you to consider in which scenarios it’s more appropriate to display the menu in this manner…

1 Comments...
  • Dave 6 October 2024, 3:05

    Very useful guide! Thanks for sharing your knowledge!

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!

Savvy WordPress Development