search

Create a Clean Navigation Menu Markup in WordPress using Walker Class

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.

This guide focuses mainly on creating a menu with clean markup using the Walker Class, making it easier to style and reducing unnecessary code in your theme.

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() and esc_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-parent class 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:

What is the Walker Class in WordPress?
The Walker Class is a PHP class in WordPress used to traverse and output tree-like data structures such as navigation menus, page lists, and category hierarchies. By extending it, you can override how each element is rendered and control the HTML output.
Why does wp_nav_menu add so many classes to menu items?
WordPress adds multiple classes by default so themes and plugins can target specific menu states like the current page, parent items, or ancestors. While useful for generic themes, this often results in bloated markup that isn't needed in custom-built themes.
Where should I place the custom Walker Class code?
Place the Walker Class in your child theme's functions.php file, or in an MU-Plugin if you want it to persist regardless of theme changes. Avoid adding it directly to the parent theme's files, as those changes will be lost on theme updates.
Can a custom Walker handle dropdown submenus?
Yes. The start_lvl and end_lvl methods in the Walker handle nested submenus. You can add custom classes or wrapper elements to the nested <ul> to style dropdown menus however you need.
Does cleaning up menu markup affect accessibility?
Removing default classes does not inherently hurt accessibility, as long as you keep proper semantic HTML (nav, ul, li, a elements) and add ARIA attributes where needed. Consider adding role="navigation" and aria-label to the nav element for screen reader support.

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.

Join the Discussion
3 Comments  ]
  • Dave 6 October 2024, 3:05

    Very useful guide! Thanks for sharing your knowledge!

  • June 23 June 2025, 3:59

    The list tag doesn’t close, need to make the following adjustment.

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

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 official logo