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 developers find the default design and behavior not aligned with their needs. There is a high demand for custom-adapted menus in WordPress themes.
The guide covers how to create and add menus in WordPress themes, but won’t go deep into menu creation and adjustments. Instead, the focus is on producing a menu with clean markup, making it easier to style later and saving some unnecessary code on the 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. 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 Developer Reference. Here are the default options:
$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. Pay attention to the first and most important parameter, theme_location. This parameter determines which menu to display based on the slug you set when registering the menu using register_nav_menu().
Displaying the Menu in a WordPress Template
To display the menu, 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 – it’s simple and clean. However, if you 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>This mess of classes and IDs can be quite frustrating. No matter which settings of the function you tweak, you won’t 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, by default, adds IDs and classes to each menu item using various filters, which in this case is less than ideal.
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. Create a PHP class in your child theme’s functions.php that “extends” Walker_Nav_Menu:
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="' . esc_url($url) . '">' . esc_html($item->title) . '</a>';
}
public function end_el( &$output, $item, $depth = 0, $args = array() ) {
$output .= '</li>';
}
}Notice the use of
esc_url()andesc_html()when outputting the URL and title. Always escape output to prevent XSS vulnerabilities, even when building a custom Walker.
Now there is a Walker that prints minimal markup. To use it, add an argument to the wp_nav_menu() function in your template:
wp_nav_menu(array(
'theme_location' => 'main-menu',
'walker' => new My_Walker_Nav_Menu(),
));You can stop here. However, if you want to clean up the markup even more, 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 the menu’s markup looks now – clean and organized:
<nav>
<ul>
<li class="active-parent"><a 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-parentclass added to the<li>element 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 more options to display clean markup when using wp_nav_menu(), and one of them involves using filters. 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 );This might seem very simple, and you might wonder why the Walker Class approach is needed at all. The limitation of using filters is that you won’t receive a specific class and indication for the currently active menu item, as the Walker provided the active-parent class earlier.
FAQs
Common questions about WordPress menu markup and the Walker Class:
Summary
This guide covered how to build a customized menu in WordPress themes and how to use the Walker Class to display the menu’s markup in a cleaner way. The Walker approach gives you full control over the HTML output while preserving active-state indicators that the filter-based alternative lacks.


Very useful guide! Thanks for sharing your knowledge!
The list tag doesn’t close, need to make the following adjustment.
Thanks June! You are correct and it’s fixed 🙂