DiggingIntoWordPress

by Chris Coyier & Jeff Starr

HTML Formatting for Custom Menus

Posted by on

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 (404 link removed 2013/04/18) 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 @ http://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:

  1. Line 3: specify your custom-menu slug
  2. Lines 8 thru 15: customize markup, tabs, and line breaks as needed
  3. 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 the clean_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

  1. 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.

    • Interesting opinion, Brando. Personally I think clean markup is worth striving for, but admittedly not for all projects. Either way it’s nice to know we have the tools to make it happen if/when needed.

      • It’s an uphill (more like upmountain) battle trying to get clean markup out of WordPress, and not one that I feel is worth the trouble in any way.

        I will take clean source code over clean markup output 100% of the time. The markup for my sites is fugly but the source code — the part that I’m actually going to read — is well structured and easy to read.

        Markup is for machines; source code is for people. Hence my preference.

        • All valid points, but you can have both: clean source code and markup. For example, this current project I’m working on, the markup is especially important (it’s a design-related site), so I’ve been learning some easy, unobtrusive ways of formatting WP’s oft unruly output. I’ll be posting these techniques here at DigWP.com and at Perishable Press as time allows, so something to look forward to ;)

        • I agree that getting clean markup out of WordPress is an uphill battle that WordPress often wins.

          It is something that will have to be tackled in the case of a design or coding portfolio where potential employers are going to be looking at your source code.

          On that note, looking forward to your article taming WP’s formatting Jeff!

        • It’s true that if its generated, it doesn’t have to be clean. It’s a nice feature and a great tut but if I am following you up in the source it better be clean!

    • 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.

      • Nah, gonna have to disagree with all of that. You have a choice for your design. Nobody is “forcing” you to do anything.

        It’s pretty simple really. If you need all the extra classes for whatever reason, then use wp_nav_menu(). If on the other hand, you would rather display your custom menus with clean, semantic markup, then use wp_get_nav_menu_items(). Both are built-in/native functions that work great at doing different things.

        Choice is a good thing, and having multiple techniques at your disposal makes you a more agile and adept developer.

        • The best solution would be the ability to strip everything except the relational style tags. It’s really all the other stuff that gets in the way. Adding a class called “current-menu-item” is the very definition of web semantics. Adding all the redundant extra classes with menu item numbers etc is not.

          So we’re 80% in agreement or thereabouts.

        • The best solution would be the ability to strip everything except the relational style tags.

          I think this is possible using wp_nav_menu() and a custom walker..?

          Adding a class called “current-menu-item” is the very definition of web semantics.

          Even when current-menu-item is not used for anything? That level of specificity isn’t required for all projects. In most cases, clean, concise markup provides all the contextual selectors required for targeting stuff like navigational menus.

          Adding all the redundant extra classes with menu item numbers etc is not.

          Agreed.

  2. 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:
    https://gist.github.com/1401532

    and this one, if you want to display the description for the nav items
    http://www.kriesi.at/archives/improve-your-wordpress-navigation-menu-output

    • Yes, this is just an easy alternate way of coding things, from the ground up using only what’s required, as opposed to fiddling with extraneous functionality.

      Thanks for the custom walkers – I hadn’t encountered either, and seeing the lastest kriesi.at design is a treat :)

  3. 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.

  4. 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?

    • Yes it is, using some PHP trickery, but nothing built into WP (that I know of) that would enable it. Best to go with a custom walker if location info is required with custom menu output.

  5. 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.

  6. 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.

    • Hey Rob, thanks for the feedback. It sounds like the FeedBurner email subscription thing may need some customizing. I’ll check into it and see what we can do to make things logical/easier to organize. Cheers :)

  7. 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?

  8. I use this, cleans the WP menu and leaves the current menu item classi tact;

    // Cleanup WP Menu html
    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);
    function css_attributes_filter($var) {
         return is_array($var) ? array_intersect($var, array('current-menu-item')) : '';
    }

    • Awesome – Thanks :) I’m looking forward to trying this method for my next design.

    • 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. )

  9. Renato Siqueira December 15, 2011

    In your book, at page 57, you talk about frameworks.
    What do you think about Pagelines?

  10. 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.

  11. 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?

    • Good questions, although it’s more of a method for creating clean markup rather than a “new HTML 5 menu”.. but I would think that clean, semantic markup is better for both SEO and faster page loading. Quality signals on both accounts.

  12. Is there a way to mark parent menu items with a class using this method?

  13. 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

    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);
    function css_attributes_filter($var) {
           return is_array($var) ? array_intersect($var, array('current-menu-item','current-page-ancestor')) : '';
    }

  14. John Harris January 1, 2012

    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: http://easywordpressblogsetupinstructions.com/2011-our-list-of-5-top-wordpress-hosting-companies/

    Thanks in advance! and more power!

  15. 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.

    • Hey nomadone, good to see you. Yeah the custom walker class certainly isn’t the only unwieldy thing WP does.. but it’s one of the most difficult to tame, in my experience – hence the tut!

      • I appreciate the tutorial Jeff, really good to get an alternative take on this and I had no idea wp_get_nav_menu_items even existed.

        I wish I had more time to scour the codex, there’s such a ton of stuff hidden away in there but examples of usage are generally a problem to come by for the less programatic types like myself.

        I’ve been finding the additional paramaters wp_nav_menu really strange as well. They don’t seem to behave the way the codex explain in all cases.
        I’d dig to see an comprehensive look at the entire list of parameters for wp_nav_menu.

        Guys like you help guys like me cope with the twilight zone moments in wordpress dev journey.

  16. 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?

    • Possibly..

      Have you actually created the menu in the WP Admin? Does it contain any menu items?

      • Yes, I did create a menu in appearance -> menus. All of the menu items are listed as a page type and I dragged and dropped them in the desired order. Does the menu need to have a specific name to work properly with the above example?

        Also, perhaps of note, I’m using WordPress 3.3 on a server running PHP version 5.2.17.

        I’ve searched for the last two days to find another example of how to use wp_get_nav_menu_items and this is the only tutorial I could find. Any help is greatly appreciated.

        • Yes, look in the code for “specify custom menu slug” and then edit the example to match your own menu. I need to include this information in the post. Apologies for any confusion :P

        • Thanks for the help with this Jeff – specifying the custom menu slug to match the name of my menu did fix the problem with the menu not appearing. However, I’ve run into another problem with this method of displaying the menu – the sub-menus are ignored and all ‘s are placed under the top level .

          Is there a trick to doing sub-menus?

        • Good question, I didn’t try with sub-menus, but it should be possible. You may want to try one of the custom walker classes mentioned in the article, they’re a little trickier to use, but are more flexible and should work with sub-menus out of the box.

  17. 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.

  18. Nick Putman June 16, 2012

    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

  19. Great post but could someone rewrite the code to include a current page item, and drop down items.

  20. 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 id's, except for those listed in the array below
    function custom_wp_nav_menu($var) {
      return is_array($var) ? array_intersect($var, array(
        //List of allowed menu classes
        '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(
        //List of menu item classes that should be changed to "active"
        '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');

Comments are closed. Contact us with any critical information. Thank you!

Code is poetry