HTML Formatting for Custom WordPress Menus
For some projects, it’s nice to output clean, well-formatted markup. Using theme template files enables great control over most of your (X)HTML formatting, but not so much for automated functionality involving stuff like widgets and custom menus. One of my current projects requires clean, semantic HTML markup for all web pages, but also takes advantage of WordPress’ custom-menu functionality to make things easy. In this DiW article, we’ll see how to enjoy both: WordPress custom menus and clean, well-formatted HTML markup.
Customizing HTML displayed with wp_nav_menu()
By default, WordPress custom menus are displayed in your theme template using the wp_nav_menu function, which by default outputs markup that looks 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>
That’s a mess of class
and id
attributes, plus an extra <div>
container to boot. Fortunately, the wp_nav_menu()
provides some useful parameters for customizing the display of your custom navigation menus. Here is a list of the default parameters:
<?php $defaults = array(
'theme_location' => ,
'menu' => ,
'container' => 'div',
'container_class' => 'menu-{menu slug}-container',
'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' => );
?>
Of these parameters, $container
lets you specify how to wrap the <ul>
element, using either 'div'
, 'nav'
, or false
. So by specifying false
for the $container
parameter, we can simplify markup by removing the <div>
container. The wp_nav_menu
function also provides two more optional parameters for customizing menu markup:
$items_wrap
– “Whatever to wrap the items with an ul, and how to wrap them with” (source: WP Codex)$walker
– “Custom walker object to use (Note: You must pass an actual object to use, not a string)” (source: WP Codex)
These sound useful, but I haven’t had much luck with either. The $items_wrap
didn’t seem to do anything, but there are plenty of custom walker scripts available for further experimentation and customization. If you can get them to work properly, a custom walker provides complete control over custom-menu markup displayed with the wp_nav_menu
tag. Unfortunately, I was unable to get very far with any of them, so I sought an alternate method..
An alternative approach for custom markup
After fiddling with a few of the various custom walkers, I decided to find an easier way to customize and format WordPress nav/menu markup. The walkers are fairly extensive and complex, and just seem like overkill for building a simple list of custom menu items. After some digging through the WordPress Codex, I found the perfect function for crafting squeaky-clean WordPress menus: wp_get_nav_menu_items. As the name implies, wp_get_nav_menu_items
returns the items from your custom navigation menus (as created in the WP Admin → Appearance → Menus panel). Thus, you can use this function to mark up your custom menus however you want. For my particular project, the desired format looks like this:
<nav>
<ul>
<li><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>
Trying to do this with the commonly used wp_nav_menu
function and a custom walker is possible, but it’s much easier building the menu structure from scratch, using only what’s required to make it happen. So alternately we use wp_get_nav_menu_items
and begin with the example function provided at the Codex. After wrapping it in a function
and customizing the output, we have the clean_custom_menus()
function, ready for copy/paste into your theme’s functions.php
file:
// custom menu example @ https://digwp.com/2011/11/html-formatting-custom-menus/
function clean_custom_menus() {
$menu_name = 'nav-primary'; // specify custom menu slug
if (($locations = get_nav_menu_locations()) && isset($locations[$menu_name])) {
$menu = wp_get_nav_menu_object($locations[$menu_name]);
$menu_items = wp_get_nav_menu_items($menu->term_id);
$menu_list = '<nav>' ."\n";
$menu_list .= "\t\t\t\t". '<ul>' ."\n";
foreach ((array) $menu_items as $key => $menu_item) {
$title = $menu_item->title;
$url = $menu_item->url;
$menu_list .= "\t\t\t\t\t". '<li><a href="'. $url .'">'. $title .'</a></li>' ."\n";
}
$menu_list .= "\t\t\t\t". '</ul>' ."\n";
$menu_list .= "\t\t\t". '</nav>' ."\n";
} else {
// $menu_list = '<!-- no list defined -->';
}
echo $menu_list;
}
After placing in your functions.php
file, you can call the function and display your custom menus anywhere in your theme by calling it directly:
<?php if (function_exists(clean_custom_menus())) clean_custom_menus(); ?>
Then, you can customize the clean_custom_menus()
function as follows:
- Line 3: specify your custom-menu slug
- Lines 8 thru 15: customize markup, tabs, and line breaks as needed
- Line 17: uncomment
else
condition and customize if needed
For lines 8-15, anything is possible – you can include whatever list items, markup, and attributes required. Additionally, you can tab and indent markup to line up with page markup using \n
for a new line and \t
for a tab space. The fastest, easiest way to use this function is to copy/paste into your theme and then check out your list markup. For example, if the nav list is too far to the left, add some more tabs. You can also add class
and id
attributes, include custom items, and even manipulate which list items are displayed ($url
, $title
, etc.). After a little fine-tuning, the wp_get_nav_menu_items()
function enables clean, well-formatted markup for your custom menus.
Remove class and ID from wp_nav_menu()
Here is another way to clean up the HTML output of wp_nav_menu()
. Just add the following snippet to your theme’s functions.php file:
function wp_nav_menu_attributes_filter($var) {
return is_array($var) ? array_intersect($var, array('current-menu-item')) : '';
}
add_filter('nav_menu_css_class', 'wp_nav_menu_attributes_filter', 100, 1);
add_filter('nav_menu_item_id', 'wp_nav_menu_attributes_filter', 100, 1);
add_filter('page_css_class', 'wp_nav_menu_attributes_filter', 100, 1);
Quick Summary
In this article, we explain two ways to clean up and customize WordPress’ custom-menu markup. Either of these methods will take your code from 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>
..to this:
<nav>
<ul>
<li><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>
..or whatever HTML structure that’s required. You can achieve this with either of these methods:
- Combine
wp_nav_menu()
with a custom walker class - Combine
wp_get_nav_menu_items()
with theclean_custom_menus()
function
As usual, if you know of better ways of doing something, please share in the comments! Thanks for reading Digging into WordPress :)
46 responses
-
Clean markup is overrated. There is very little reason to care about how the markup winds up looking in the end. Unless you’re trying to cache things and send the bare minimum of data, there is actually no to bother.
The deluge of CSS classes are there for your convenience should you need them, and with this solution you lose out on very handy navigation CSS such as
current-menu-item
and the ability to target specific items as needed.-
Well as a mobile web user not on an unlimited data plan, every byte saved is good news, as usual “it depends” is the best answer. I do what Jeff demonstrated but if I can keep current-menu-item or target that another way with CSS (not looked yet) that would be the icing on the cake.
Thanks Jeff.
-
I concur. The current-menu-item, and various child classes are vital to building an agile and understandable user experience. Stripping out this code forces developers to come up with other more clunky solutions to do what WordPress already does by default.
-
-
Thanks for this, it’s always useful to learn about alternative ways to do things. Also, I’m glad I’m not the only one stumped by the walker method.. I’ve used 2 different custom walkers on a few projects:
for responsive designs for example, you can create a select dropdown menu.
And this one, if you want to display the description for the nav items.
-
I personally like clean markup, which has always been tough using WP. Having the “current-menu-item” class is handy, but much of the rest of that string of classes are not useful. Thanks for sharing this GREAT information! This will come in handy.
-
I’m with you on clean code and markup, possibly there’s a bit of OCD in me. I was curious, however, about the ability to use the current menu functionality. Is there any way to use the method you’ve provided with the ability to detect the current location?
-
I’ve been struggling with this for almost a year. My goal is to find a way to strip out all of the WordPress mess and add the classes of first and last to the appropriate ‘s on the parent and child menus. I still can’t find a way to do it. I wrote a detailed post in the WordPress forums (see here) about my dilemma and what little rationale I have for my madness.
-
I bought your book and get your tips/articles as emails. Most of them are great so I save them in a folder for reference. Will you please consider adding the actually topic to the subject line instead of always just making it “Digging into WordPress?” It would make it a lot easier to search the folder and find what I’m looking for.
-
This looks like what I want but I’m having trouble configuring the code for sub-menus. Not sure if I want to use this method or find a way to do it with the WordPress Walker. What would you suggest?
-
I use this, cleans the WP menu and leaves the current menu item classic tact;
// Cleanup WP Menu html function css_attributes_filter($var) { return is_array($var) ? array_intersect($var, array('current-menu-item')) : ''; } add_filter('nav_menu_css_class', 'css_attributes_filter', 100, 1); add_filter('nav_menu_item_id', 'css_attributes_filter', 100, 1); add_filter('page_css_class', 'css_attributes_filter', 100, 1);
-
My above solution achieves the same thing, but leaves off the empty classes from the li element. Maybe not a big deal to most people, but makes the markup just a touch more clean.
The only thing keeping it from a perfect solution, in my books anyway, is a way of adding the class of “first” and “last” to the appropriate li’s on top level and sub menus.
-
@Ashley
Wow, your solution is the answer.
@Jeff
Thank you for all the great stuffs.
I hope you don’t mind putting this up as an update in this post in hope that it might be useful to many others ( not many will browse thru the comments – it’s better to just add it up in the post. )
-
-
In your book, at page 57, you talk about frameworks.
What do you think about Pagelines? -
Thank you for this article, I’ve trying to do something like this for a while and just gave up, was annoyed that I couldn’t get ‘my’ markup to appear exactly as I wanted it, especially the ugly classes and ids generated by wp and replace them with mine.
-
Good article and i have question about Search engine related query.
Is this new HTML 5 menu helps SEO?
or useful for faster page loading? -
Is there a way to mark parent menu items with a class using this method?
-
Using the function below, I was able to flag the “current-page-ancestor” as well as the “current-menu-ite.” Worked like a charm:
// Cleanup WP Menu html function css_attributes_filter($var) { return is_array($var) ? array_intersect($var, array('current-menu-item','current-page-ancestor')) : ''; } add_filter('nav_menu_css_class', 'css_attributes_filter', 100, 1); add_filter('nav_menu_item_id', 'css_attributes_filter', 100, 1); add_filter('page_css_class', 'css_attributes_filter', 100, 1);
-
Hi Jeff,
Nice article! That was very helpful, especially the clean custom menu functions. I really would like to hear your take on WordPress Hosting, I need to compare opinions with mine here.
Thanks in advance! and more power!
Editor’s note: 404 link removed.
-
Thanks a ton Jeff, firstly great to get an alternative method to dealing with custom menu markup, and also welcoming (i think) to know that someone of your level of skill also has a problem with the custom walker class thing.
I can’t for the life of me understand why achieving common sense type tasks in WordPress sometimes means needing to develop hacker like abilities.
I’m writing some beginner tutorials on WordPress theme development for newbies, and while experimenting with various iterations and scenarios custom menus for the menu’s tut, I find many of the custom menu parameters to behave really strangely, especially the container
-
I can’t for the life of me understand why achieving common sense type tasks in WordPress sometimes means needing to develop hacker like abilities.
Yes, ditto on this one.
A lot of times, I will see WP Core developers respond to this kind of matter as if there’s nothing wrong with their thinking process, and push this back to the users side instead – if the users understand how WP works, this will not be a problem.
And they made this a philosophy of WP, like decisions over options attitude, and yes, it’s only a few people who draw the line.
-
-
Maybe there is something missing out of this tutorial, or maybe I’m doing something wrong…but I can’t get the code samples in this tutorial to work at all. I copied and pastied the function that starts with
function clean_custom_menus()
in my functions.php file. Then I copied and pasted
<?php if (function_exists(clean_custom_menus())) clean_custom_menus(); ?>
into my page template file. There is no menu. Is there a third step I’m missing?
-
As I recently met this problem of building a well structured navigation, I think the main reason me not using
wp_get_nav_menus
function was that it gives back the navigation items without any hierarchy. So I will not be able to ‘reconvert’ it to the same hierarchy as I arranged the items in the custom menus widget.// Anyway I agree, it would be nice to have a function that outputs a nice, clean HTML structure without the unnecessary junk labels… I would need a better control though.
-
Hi,
Thanks for this function. I am trying to implement it, but for some reason the custom menu I have implemented isn’t getting picked up by the script. I have double checked that I have entered the correct menu name in your function. When I add:
if (!isset($locations[$menu_name])) { echo "not found"; }
..”not found” is returned.
Any ideas why this is not working?
Thanks,
Nick
-
Great post but could someone rewrite the code to include a current page item, and drop down items.
-
I’ve figured it out, but I use a completely different method that involves a code snippet I place in my functions.php file. Here is what I do…
// Deletes all CSS classes and ids, except for those listed in the array below function custom_wp_nav_menu($var) { //List of allowed menu classes return is_array($var) ? array_intersect($var, array('first', 'last', 'current_page_item', 'current_page_parent', 'current_page_ancestor', 'current-menu-ancestor', 'active')) : ''; } add_filter('nav_menu_css_class', 'custom_wp_nav_menu'); add_filter('nav_menu_item_id', 'custom_wp_nav_menu'); add_filter('page_css_class', 'custom_wp_nav_menu'); // Replaces current-menu-item (and similar classes) with active function current_to_active($text) { $replace = array('current_page_item' => 'active', 'current_page_parent' => 'active', 'current_page_ancestor' => 'active', 'current-menu-ancestor' => 'active'); $text = str_replace(array_keys($replace), $replace, $text); return $text; } add_filter ('wp_nav_menu','current_to_active'); // Deletes empty classes and removes the sub menu class function strip_empty_classes($menu) { $menu = preg_replace('/ class=""| class="sub-menu"/','',$menu); return $menu; } add_filter ('wp_nav_menu','strip_empty_classes');