WordPress provides the flexibility to utilize Walker classes for the purpose of navigating and presenting elements within a hierarchical structure. This article delves into the creation, implementation, and customization of a personalized walker class to tailor the output of menus.
While the most common application of Walker classes in WordPress involves customizing menus, it’s important to note that these classes are employed across various scenarios. WordPress utilizes Walker classes not only for menu customization but also for tasks such as rendering taxonomy hierarchies, comment hierarchies, wp_list_pages()
, and wp_list_categories()
. These functionalities all inherit from a foundational Walker
class. In this guide, we will specifically extend the Walker_Nav_Menu
, which serves as the base class for menu-related operations in WordPress.
Because we extend another class we need only add the functions we wish to override. If a function does not exist in our class, WordPress will run the parent class’ (the class we extend) function instead.
You can add your walker class in your plugin files, theme’s function.php
or any PHP file included by functions.php
(for cleaner code). You start by defining your class by a name of your choosing (make sure the class name is unique, and this includes possible class names in WordPress core!) extending Walker_Nav_Menu
:
class custom_walker extends Walker_Nav_Menu { }
In order to tell WordPress to use our walker, we define this in our wp_nav_menu()
calls. This function is responsible for outputting a menu and you probably has at least one in your theme for the main menu.
In the argument array to wp_nav_menu()
you add a new element with the key ‘walker’ and creates a new instance of your walker class like so:
wp_nav_menu([ 'theme_location' => 'primary', 'menu_class' => 'main-menu', 'container' => 'nav', 'container_class' => 'header__main-nav', 'walker' => new AWP_Menu_Walker() ]);
If you refresh your site you should see no change. This is because our class does not override any of the parent’s functions, and thus WordPress simply runs the normal menu walker functions when outputting the menu, just like before we told it to use our walker.
Walker_Nav_Menu
The following are functions you can add to your custom walker class to override the parenting class Walker_Nav_Menu
funtions:
The first four are functions that are simply responsible for outputting, and they all require you to append to a string – the first parameter variable. It’s important to know that you don’t echo
anything out here, everything is supposed to be built up as a string.
The function start_lvl
is responsible for outputting the HTML for the start of a new level. In short it should output the starting <ul>
.
function start_lvl(&$output, $depth=0, $args=null) { }
The first parameter, $output
– passed by reference, is the string you’ll append your output to. $depth
is an integer signaling which level you’re at; 0 for top-level, 1 for direct child of top-level, and so on. $args
is an object of all arguments provided in wp_nav_menu()
.
The end_lvl
function is responsible for outputting the HTML for the end of a level. This is usually just the closing </ul>
.
function end_lvl(&$output, $depth=0, $args=null) { }
The parameters are the exact same as for start_lvl
above.
This function is responsible for outputting each element’s HTML. In short it should output the starting <li>
and the <a>
tag with the link title inside.
function start_el(&$output, $item, $depth=0, $args=null, $id=0) { }
The first argument, $output
, is as usual the string you’ll append the output to. The second argument, $item
, is the menu item object – and this is where you’ll fetch most of the data for outputting the menu item. If the menu link is a post menu item, you’d get the post object here. Regardless of menu type, you’ll also get some additional useful elements; such as classes
, url
, title
, and description
.
The third argument, $depth
, is an integer telling you which level we’re at. Level 0 is top-level, 1 is direct child of top-level, and so on. The fourth argument, $args
, is an object of all arguments provided to wp_nav_menu()
. The fifth parameter, $id
, is the current menu item ID.
The end_el
function is responsible for outputting the closing of an element. Usually it would just output the </li>
tag.
function end_el(&$output, $item, $depth=0, $args=null) { }
The arguments for end_el
are the same as start_el
above except that the function doesn’t have the fifth parameter, $id
.
The function display_element
is an inherited function from the general Walker
class, and is the function responsible for traversing. This is the function that calls all of the above functions in turn.
I’m including this here because in some cases, for example if you want to prevent traversing a whole branch, you’d use this function for that.
function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) { }
The first argument, $element
, is the menu item object – this is what is passed down as $item
in the above functions. The second argument, $children_elements
– passed by reference, contains all child elements this function will traverse. $max_depth
, the third argument, is an integer that signals how deep we should traverse, and the fourth argument, $depth
, is the depth we’re currently at. The fifth argument, $args
, is the arguments passed to the function that called the walker (for menus it would be the arguments provided to wp_nav_menu()
), and the final argument, $output
– passed by reference, is the output which is passed down as first argument in all of the above functions.
In the overview above you should see that the function start_el()
is the one responsible for outputting the HTML for a single menu element. Let’s start by overriding this function in our walker class with a simple example.
Let’s make sure that any ‘#
‘ links gets a <span>
element instead of a link tag, to avoid refreshing the page.
class AWP_Menu_Walker extends Walker_Nav_Menu { function start_el(&$output, $item, $depth=0, $args=[], $id=0) { $output .= "<li class='" . implode(" ", $item->classes) . "'>"; if ($item->url && $item->url != '#') { $output .= '<a href="' . $item->url . '">'; } else { $output .= '<span>'; } $output .= $item->title; if ($item->url && $item->url != '#') { $output .= '</a>'; } else { $output .= '</span>'; } } }
We’ll start the element by appending a <li>
tag to $output
. We want to make sure that WordPress’ default classes (for example ‘menu-item’, ‘menu-item-has-children’ etc), as well as classes entered manually in Menu editor gets added to our list element. We glue the classes provided as an array in $item->classes
using the PHP function implode()
separating each element with a space.
At line #5-9 and #13-17 we handle the conditional output of the wrapping element. We output a <a>
tag, unless the element’s URL is ‘#
‘ in which case we provide a <span>
tag instead. At line #11 we simply output the link’s text, which resides in $item->title
.
This is all we need for making sure all menu elements that has ‘#
‘ as URL isn’t clickable!
If you are doing this in a styled theme, keep in mind that you might lose some styling if the theme has styled the <a>
tag directly. You can solve this by changing the styling and possibly adding a class to the span element.
As an example another thing you can do here is outputting the menu description. This exists, but is not activated as default. In WordPress Menu editor you need to click “Screen Options” in the top right, and check off for showing “Description”:
This allows the user to enter a description to each element. You can output this description in your walker class. Let’s say you only want to show description for the top-level items, as this is a part of your theme’s design. You can simply check if the $item
has a description and if $depth
is 0, like so
... $output .= $item->title; if ($depth == 0 && !empty($item->description)) { $output .= '<span class="description">' . $item->description . '</span>'; } ...
A more common and useful example is adding a “caret”, an icon that signals that this menu item has a dropdown menu (has child elements).
You’ll need to figure out your caret HTML output. In my case I’m outputting an <i>
item with specific classes for a nice down arrow available by the Fontawesome library which provides thousands of icons. You also want to ensure this caret only outputs on elements that has children. The best way I’ve found to figure out if the current element has children, is by referencing the walker object (yes, which is our walker itself, but also the classes it extends!) in $args
, and checking the boolean has_children
. Outputting a caret is as simple as:
if ($args->walker->has_children) { $output .= '<i class="caret fa fa-angle-down"></i>'; }
The complete walker class would look like this:
class AWP_Menu_Walker extends Walker_Nav_Menu { function start_el(&$output, $item, $depth=0, $args=[], $id=0) { $output .= "<li class='" . implode(" ", $item->classes) . "'>"; if ($item->url && $item->url != '#') { $output .= '<a href="' . $item->url . '">'; } else { $output .= '<span>'; } $output .= $item->title; if ($item->url && $item->url != '#') { $output .= '</a>'; } else { $output .= '</span>'; } if ($args->walker->has_children) { $output .= '<i class="caret fa fa-angle-down"></i>'; } } }
And that’s all you need for ensuring your menu gets nice caret icons on parent elements and that ‘#
‘ links won’t be clickable.
If you want the caret icon to change, for example into an up arrow when the dropdown is active, you will need to add this with Javascript to your theme.
As the examples above suggest, you can manipulate the output however you’d like, based on any conditionals. You can for example modify the output based on if a certain class is present (for example a class manually entered in Menu editor) by looking for the class in $item->classes
, or you can manipulate (for example capitalize) the outputted item text provided in $item->title
.
wp_nav_menu
I would like to mention another useful thing. Remember that $args contains all arguments provided to wp_nav_menu()
. This includes for example theme_location
and others, so if you can modify the output only for specific theme locations – for example main menu. But you can actually provide any custom arguments!
Say you are outputting the same menu multiple times, for example one for desktop and again for mobile. Or you want your walker to manipulate the items only when they are output by wp_nav_menu()
in your theme, and not when the menu is added through a widget? Perhaps you want your walker to handle the output differently in these cases?
You can provide any custom arguments to wp_nav_menu()
. As a simple example, I’ll add a boolean ‘show_carets
‘ to the arguments to ensure that carets are added only in those cases I want them – instead of my walker class adding carets to all menus.
wp_nav_menu([ 'theme_location' => 'primary', 'menu_class' => 'main-menu', 'container' => 'nav', 'container_class' => 'header__main-nav', 'walker' => new AWP_Menu_Walker(), 'show_carets' => true ]);
Then I can simply change my caret-adding piece of code above (line #19-21) into checking whether or not show_carets
is present and true in $args
, like so:
if ($args->show_carets && $args->walker->has_children) { $output .= '<i class="caret fa fa-angle-down"></i>'; }
You can add any arguments you’d like ensuring your walker only customizes the menus you want to. For example simple booleans for different cases, e.g. is_mobile_menu
, or anything else you need.