Create a custom WordPress menu programmatically with wp_nav_menu

The wp_nav_menu function can be a challenging aspect of WordPress development, particularly when attempting to create a custom WordPress theme while maintaining complete control over the HTML markup.

In this article, we will explore the various techniques and approaches to fully customizing WordPress menu HTML, including adding you own custom CSS classes to <a> tags, <li> tags, icons, and more.

Create a menu with wp_nav_menu

Before getting into the details of customizing a WordPress menu’s HTML, it is essential to understand the basics of the wp_nav_menu function. This function is responsible for generating navigation menus in WordPress, allowing you to define and output menus in your theme.

Before using the wp_nav_menu function in your theme, you need to register your custom menus in your functions.php file. Here is a an example.

<?php
function register_menus(){
    register_nav_menus( array(
        'primary' => __( 'Primary Menu', 'my_theme' ),
    ) );
}

add_action( 'after_setup_theme', 'register_menus', 0 );
?>

The name of the function “register_menus” can be anything you like, just as long as you reference the same function name in the second parameter of the “after_setup_theme” action.

Once in place, you will see the “Primary Menu” as a selectable option in the Appearance->Menus area of the admin and can add menu items to it.

Now we are finally ready to add the wp_nav_menu to our header.php file.

Understanding the Parameters of wp_nav_menu

The wp_nav_menu function accepts an array of parameters that control its behavior. Most of them are pretty self-explanatory in the docs, such as adding various classes or IDs. Others are a bit more complicated, such as adding a custom walker class.

Here is an example menu with all the default parameters. (I added my own custom values for the theme_location, menu_class, and container parameters).

<?php
	wp_nav_menu(
		array(
			'theme_location'       => 'primary',
			'menu_class'           => 'flex gap-5',
			'menu'                 => '',
			'container'            => 'nav',
			'container_class'      => '',
			'container_id'         => ''
			'container_aria_label' => '',
			'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>',
			'item_spacing'         => 'preserve',
			'depth'                => 0,
			'walker'               => '',
		)
	);
?>

Here is the rendered output based on the above wp_nav_menu function.

<nav class="menu-header-menu-container">
	<ul id="menu-header-menu" class="flex gap-5">
		<li id="menu-item-215" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-215">
			<a href="http://joey-augustin.local/programming/">Programming</a>
		</li>
		<li id="menu-item-216" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-216">
			<a href="http://joey-augustin.local/software/">Software</a>
		</li>
		<li id="menu-item-18" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-18">
			<a href="http://joey-augustin.local/about/">My Story</a>
		</li>
	</ul>
</nav>

As you can see, WordPress adds a number of CSS classes based on either the slug of the menu itself, or the IDs of each menu item in the database.

What is the difference between wp_nav_menu and Walker_Nav_Menu class?

The Walker_Nav_Menu class helps us traverse a flat data structure to create a nested one, setting up the HTML ready for output.

The wp_nav_menu function, on the other hand, layers on additional methods and parameters to create the final desired markup by adding things like IDs, classes and attributes to parts of the markup.

In other words, when you save a menu item using the Appearance->Menu interface, WordPress saves each menu item to the database. One of the fields in the database signifies the “parent” ID of the menu item, if it has one.

However, if we want to create menus and submenu items (such as with a dropdown menu), we need to understand the relationship between parent and child menu items, and then appropriately output the correct nested markup structure.

Since each method needs to also traverse the data and inject things like CSS classes in the right places, the wp_nav_menu function utilizes the Walker_Nav_Menu class in order to do that.

This is why there is a parameter in the wp_nav_menu to add your own custom walker class, which can simply inherit all the methods from Walker_Nav_Menu while you inject your own customizations.

How do I remove the div wrapper from wp_nav_menu?

You can simply set ‘container’ => false when calling the wp_nav_menu function.

I typically prefer to set the ‘container’ parameter to <nav> and add my own classes if necessary by using the ‘container_class’ parameter.

How do I remove or change the <ul> from wp_nav_menu?

The default value for the ‘items_wrap’ parameter:

'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>'

If you set the value to just %3$s in ‘items_wrap’ it would remove the <ul>, but keep the <li> tags in tact. These unusual looking strings are a way of creating a placeholder, that will later be interpreted as a string using the sprintf function.

Here is the actual line of code in the wp_nav_menu function definition:

$nav_menu .= sprintf( $args->items_wrap, esc_attr( $wrap_id ), esc_attr( $wrap_class ), $items );

The sprintf function accepts a string as the first parameter, which in this case is the value we set for the items_wrap argument. The subsequent parameters will replace each designated area from the string we pass in. So esc_attr( $wrap_id ) would replace %1\$s from the string.

Please note, removing the <ul> or <ol> is not a great idea, unless you wanted to create your own <ul> or <ol> outside of the function, in the header.php template. Even then, you would not be able to output proper nested markup if you had submenu items.

How do I add custom CSS classes to <a> tags and <li> tags with wp_nav_menu?

The wp_nav_menu function does not provide the ability to add classes to <a> tags and <li> tags by default, but there are hooks/filters available.

In this case, the filters are “nav_menu_link_attributes” for <a> tags and “nav_menu_css_class” for <li> tags. You can read more about what hooks/filters are available to the wp_nav_menu function here.

You will need to add these filters to your functions.php file, and define a function that will get executed for each filter.

Here are two examples that will allow you to add different CSS classes to <a> tags and <li> tags based on the “level” they are. (For example, the root <ul> would be level 0, and a sub-menu <ul> is level 1, and so on.)

// Add custom classes to Menu List Item elements by depth
function add_li_class( $classes, $item, $args, $depth ) {
    if ( isset( $args->li_class ) ) {
        $classes[] = $args->li_class;
    }

    if ( isset( $args->{"li_class_$depth"} ) ) {
        $classes[] = $args->{"li_class_$depth"};
    }

    return $classes;
}

add_filter( 'nav_menu_css_class', 'add_li_class');

// Add custom classes to Menu Anchor Item elements by depth
function add_link_attrs( $atts, $menu_item, $args, $depth ) {
    if ( isset( $args->anchor_class ) ) {
        $atts['class'] = $args->anchor_class;
    }

    if ( isset( $args->{"anchor_class_$depth"} ) ) {
      $atts['class'] = $args->{"anchor_class_$depth"};
    }

    return $atts;
}

add_filter( 'nav_menu_link_attributes', 'add_link_attrs');

With these filters in place, we would use them in our wp_nav_menu function like so:

<?php
    wp_nav_menu(
        array(
            'theme_location' => 'primary',
            'li_class_0'     => 'my-class' // Adds classes to level 0 li tags
        )
    );
?>

Advanced Menu Customization with the Nav_Walker_Class

Hooks/filters along with default parameters for wp_nav_menu offer significant flexibility in customizing WordPress menus.

However, they may not provide enough complete control over the generated HTML structure, especially if you are looking to create a custom plugin. (A menu icon plugin, for example, allowing you to add an icon input feature to inject into the menu).

The Nav_Walker_Class provides a more advanced and comprehensive approach to menu customization.

Understanding the Nav_Walker_Class

When working with the Nav Walker class, it is essential to remember that WordPress menu items are stored as a tree-like data structure, where each item has a parent and potentially one or more children. The Walker class, when iteratively traversed, produces a ‘depth-first’ output. This depth-first approach allows for the generation of nested lists, or sub menus.

Creating a custom walker class extends the default Walker class to override some of its methods and add custom functionality. To add custom icons to WordPress menu items, you would usually override the ‘start_el’ function, which is responsible for generating each individual menu item. This function can be modified to append or prepend custom HTML, such as an icon, to each item.

Here’s an example of a custom walker class:

class Custom_Icon_Walker extends Walker_Nav_Menu {
    function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
        $icon = get_post_meta($item->ID, '_menu_item_icon', true); // Let's say you store your icons in a custom meta field
        $indent = ($depth) ? str_repeat("\t", $depth) : '';
        $output .= $indent . '<li><a href="' . $item->url . '"><i class="' . esc_attr($icon) . '"></i> ' . $item->title . '</a>';
    }

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

In the above example, we’re extending the default ‘Walker_Nav_Menu’ class and overriding the ‘start_el’ and ‘end_el’ methods. The ‘start_el’ method is where we insert our icon. This assumes that we’re storing our icon class names in a custom post meta field, which you could do with ACF or .

Finally, after creating the custom walker class, you need to use it when calling ‘wp_nav_menu’ in your theme. This is done by setting the ‘walker’ argument to an instance of your custom class like this:

'walker' => new Custom_Icon_Walker(),

Similar Posts