How to Tidy Your WordPress Menu HTML

Contributing Editor

I love WordPress. I also love clean semantic HTML. Unfortunately, several of the standard WordPress theme functions return code that is a little untidy. For me, the primary culprits are wp_list_pages() and the newer wp_nav_menu(); both return an unordered list of page links typically used for page menus and sitemaps, e.g.,


<?php wp_nav_menu(array( 'depth'=>2 )); ?>

The code results in this HTML abomination for the default installation’s home, about and contact pages:


<div class="menu">
<ul>
<li >
<a href="http://mysite.com/" title="Home">Home</a>
</li>
<li class="page_item page-item-2 current_page_ancestor current_page_parent">
<a href="http://mysite.com/about" title="About">About</a>
<ul class='children'>
<li class="page_item page-item-5 current_page_item">
<a href="http://mysite.com/about/contact-us" title="Contact us">Contact us</a>
</li>
</ul>
</li>
</ul>
</div>

The code is valid but it contains items we generally don’t need:

  • Strictly speaking, the outer div isn’t required. I’d prefer either to give the ul an ID such as “navigation” or use the HTML5 nav element.
  • We don’t need a title attribute when our link contains identical text.
  • Does our CSS or JavaScript require hooks for “page_item” and “page-item-N” classes?
  • The “children” class for the sub-links list isn’t necessary — we can style them using a selector such as “nav ul ul li.”
  • The current_page_ancestor and current_page_parent classes mean the same thing, but I’d prefer a single shorter name such as “open.”
  • Similarly, I want rename current_page_item to “active.”
  • Do we require the full page URLs — we could use shorter absolute addresses such as /, /about and /contact?

There are several ways to tidy the HTML, but the simplest solution replaces strings using regular expressions.

note: The WordPress 3 Walker object
In WordPress 3.0, a custom Walker object can be passed as an argument to wp_nav_menu(). The object provides code to output your own custom HTML for every page link. While this will be useful in some circumstances, you’ll possibly require regexs for the outer HTML, the code won’t necessarily be shorter, and it won’t work in WordPress 2.x and below.

Here’s the PHP code to output a tidier HTML menu to 2 levels (main menu and sub-menu). In most cases, it should replace the call to wp_nav_menu() or wp_list_pages() in your theme’s header.php file:


echo preg_replace(array(
    '/t/', // remove tabs
    '/'.str_replace('//','//', get_bloginfo('url')).'/i', // remove full URL
    '/current_page_items*/i',
    '/current_page_ancestors*/i',
    '/current_page_parents*/i',
    '/page_items+/i',
    '/page-item-d+s*/i',
    '/childrens*/i',
    '/s*class=["']["']/i', // empty classes
    '/s*title="[^"]+"/i', // all titles
    '/s+>/i',
    '/div>/i' // change div to nav
  ),
  array(
    '',
    '',
    'active',
    'open',
    '',
    '',
    '',
    '',
    '',
    '',
    '>',
    'nav>'
  ),
  wp_nav_menu(array( 'menu_class'=>'', 'depth'=>2, 'echo'=>false ))
);

If you’re using a version of WordPress prior to version 3, replace the penultimate “wp_nav_menu(…)” line with:

"<nav><ul>n"
  . wp_list_pages('depth=2&title_li=&sort_column=menu_order&echo=0')
  . "</ul></nav>"

Our resulting HTML is much cleaner and has been reduced by more than 50%. Longer menus may result in larger savings.


<nav>
<ul>
<li><a href="/">Home</a></li>
<li class="open">
<a href="/about">About</a>
<ul><li class="active"><a href="/about/contact-us">Contact us</a></li></ul>
</li>
</ul>
</nav>

Please note that regular expressions are powerful but dangerous. You may need to change the code if you’re using a deeper page depth or have a page named “children” or “page_item.”

There’s no excuse now — go and tidy your WordPress HTML!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • aoberoi

    this is certainly VERY useful, so much so that I think this type of trimming should be in wordpress core. the verbose version should be opt-in, not the default.

    • http://www.optimalworks.net/ Craig Buckler

      Thanks aoberoi. The reason for some of those classes is backward compatibility. The WordPress team don’t want to break existing themes so some older code remains.

      That said, it would have been great if they’d considered it for the newer wp_nav_menu() function.

  • http://xavisys.com aaroncampbell

    To get rid of the wrapping div do this:
    wp_nav_menu( array( 'container' => '' ) );

    To use a nav element do this:
    wp_nav_menu( array( 'container' => 'nav' ) );

    There are plenty of other options worth looking into too!

    • http://www.optimalworks.net/ Craig Buckler

      I couldn’t get it working for wp_nav_menu() in my WP installation? It does for wp_list_pages() though?

      • http://xavisys.com aaroncampbell

        I had tested on a trunk install before posting here and it worked fine. I just tested on a 3.0.4 install and it works fine there too. It’s messy because I didn’t style anything and just dumped the following code at the top of the document, but you can see it in action here:
        http://xavisys.com/clients/genesis/
        The exact code used was:
        wp_nav_menu( array( 'container' => '', 'theme_location' => 'primary' ) );
        wp_nav_menu( array( 'container' => 'nav', 'theme_location' => 'primary' ) );
        The “‘theme_location’ => ‘primary’” thing is just to tell it which nav menu from that site to use.

  • BrenFM

    Thanks for the code, Craig! Helpful as always.

    I might suggest though that for more complex sites, current_page_parent and current_page_ancestor are two very different things. Two sites I’ve worked on this year have nav going 7 deep and I still need to easily tell the difference between an ancestor page and an unrelated pseduo-sibling page. But thanks for the help on the Regex stuff… it’s a totally foreign language to me!

    • http://www.optimalworks.net/ Craig Buckler

      You have navigation to 7 levels?!! You will need to be a little careful in that case — the regexs may replace items you want to keep.

      • BrenFM

        Yeah built a theme around some existing content for http://www.hrc.co.nz – can’t recall which tree it was but one of them is 7 deep! Point being though that I shan’t be using your code on that site! ;)

  • CharlieLaw

    Thanks for the script, it was doing my head in having all those unnecessary ids and class names on the list items.

  • astrotim

    This is awesome, I had often wondered if there was a way to make WordPress ‘put a sock in it’, so to speak.

    I will incorporate this into my article about using wp_nav_menu to create a jQuery drop down menu in WordPress 3 http://www.astronautdesigns.com/2010/11/wordpress-3-jquery-drop-down-menu/ to make the end result nice and clean :)

  • garison54

    “the outer div isn’t required. I’d prefer either to give the ul an ID”
    It may be just me, but I have a lot of trouble with certain browsers styling the backgrounds of ULs in my menus. I find it easier to wrap the menu in a container and style that.

    “The “children” class for the sub-links list isn’t necessary”
    It might be, in some cases. If you assign a unique class name to each sub-menu, you can provide individual styles for them. You’d be surprised how many people want different colors for each of the drop-downs.

    “Do we require the full page URLs”
    If your site has a secure side, then yes. If you can tweak your code to only provide full URLs if the protocol is different, then do that; otherwise, keep the full URLs.

    • http://www.optimalworks.net/ Craig Buckler

      If you’re having trouble styling a background UL, it’s possibly because its child LI elements are floated so the UL collapses to nothing. Floating the UL or an overflow:auto may help.

      The “children” class is a fixed name so it won’t help you style sub-menus in different colors. You can use CSS3 selectors to target them though.

      You’re absolutely right that HTTPS pages will require the full URL if you’re switching back to HTTP. But I doubt many WordPress sites use it.

    • Chief Alchemist

      Me too. IE seems to ignore certain stylings when applied to ul. Maybe it’s just me?

      • http://www.optimalworks.net/ Craig Buckler

        Could you provide some sample code where it occurs? I expect it can be fixed and I’d be happy to write a post about it.

  • http://www.qnq.com.au Mike Hudson

    Great minds must think alike ;), Roger Johannson recently published a similar article, including the use of the Walker function in addressing verbose HTML output from menus – http://www.456bereastreet.com/archive/201101/cleaner_html_from_the_wordpress_wp_list_pages_function/

    • http://www.optimalworks.net/ Craig Buckler

      Thanks Mike. Roger beat me to it! His solution is slightly different since he uses a filter to call the regex function. That’s a nice idea if you want the filtering applied every time.

      He also illustrates the Walker function method. As you can see, it’s a lot more complicated!

  • http://www.mikehealy.com.au cranial-bore

    If only the WordPress source could be filtered to not break into and out of PHP tags for every line of code.

    <?php echo ‘It ‘; ?>
    <?php echo ‘is ‘; ?>
    <?php echo ‘very ‘; ?>
    <?php echo ‘annoying ‘; ?>

    • http://www.optimalworks.net/ Craig Buckler

      You don’t need to do that if you’re developing your own themes but, I agree it seems to be a style many WordPress coders adopt.

  • Kevin

    Is there much of a performance hit from using the regular expression?
    Is it not possible to do something similar using function.php?

    • http://www.optimalworks.net/ Craig Buckler

      Regexs can be expensive. The code above doesn’t use anything too complicated so they’re unlikely to cause any noticeable performance hit. Besides, if you’re using a plug-in such as WP Super Cache, the code is only generated once.

      You can put the code within function.php and call it there. I’ve often done that to implement menu shortcodes.

  • http://www.deathshadow.com deathshadow60

    Thank you for addressing my #1 dislike about turdpress, the way it barfs up menus with the “I have no clue how to use HTML” unneccessary classes, full URL’s, and redundant title attributes.

    Of course if you are using javascript on a menu for anything more than IE6, you’ve probably already shtupped the puppy — but then with people throwing 100k libraries of javascript nonsense like jquery and mootools for ABSOLUTELY NOTHING USEFUL it’s not that surprising to see such rubbish markup to support it either.

  • kaf

    It certainly looks cleaner now. (though my main problem with it was the lack of indenting)
    I agree with you that the classes were messy.
    I think you may need to retain the titles though. They are used by accessibility programs. They speak the title out to them so they can navigate the site. Someone please correct me if I’m wrong in this. It will save me lots of time.
    Also I always keep absolute urls. If for no other reason, what if the template is for an email? (not that you will have multi level menus in an email but you could use it for one level)
    Also what if you have different domains going to different sub-sections of the site? You have to know which domain you are using in the link. I don’t know if you can do that in wp but my clients ask for it all the time.
    I kinda think of relative urls as a throwback to the days when we wrote all of our src attributes by hand instead of letting the CMS generate them. Portability isn’t an issue any more.
    Just curious, I don’t know much about wordpress, but are you worried about the performance hit of doing that preg_replace on every page of every site? Or are the results cached?

    • kaf

      Sorry, looks like someone already asked my question about the performance.

    • http://www.optimalworks.net/ Craig Buckler

      Titles are only necessary for accessibility if they’re different to the content within the anchor, e.g. a short ambiguous menu item could be expanded. As it is, some screen reader will read out Home, Home, About Us, About Us, etc.

      The example above is only intended for menus and sitemaps which are on the same sub-domain/domain. They’re not relative — they’re absolute, but omit the domain name. There’s unlikely to be a problem with URLs for 99% of WordPress-powered sites. Besides, you can always forward a domain to a specific page if necessary.

      Finally, I have no worries about the performance hit of preg_replace. I’ve been using code like this on WP sites for many years. Caching will always help speed up your site, though.

  • http://www.lunadesign.org awasson

    Nice bit of info but isn’t the reason for the classes so that you can theme the menu with more advanced CSS for rollovers and current page indicators? Also I would think that the titles on links provide an improved level of accessibility.

    • http://www.optimalworks.net/ Craig Buckler

      The WP classes can help if you want to apply a specific style to every link. That’s rarely necessary, though, and I wouldn’t be confident in creating CSS rules for classes using page IDs such as “page-item-5″.

      As mentioned, the title attribute is only necessary if you need to expand on or provide alternative content for the anchor text. It’s redundant in <a href="/" title="Home">Home</a> and could hinder accessibility.

      • http://www.lunadesign.org awasson

        I wouldn’t be confident in creating CSS rules for classes using page IDs such as “page-item-5″

        Ya think…. LOL…

        I was considering the more useful classes like:
        “page_item”,
        “current_page_ancestor” and
        “current_page_parent”

        Those are very useful and I would most definitely write CSS rules for those to provide visual queues for web visitor to get a better idea of where they are within the site.

      • http://www.deathshadow.com deathshadow60

        True for the numbered/targeted ones — IF you need that, but most well written themes shouldn’t unless you are adding icons (useful) or doing something stupid like images for text. (garbage accessibility)

        BUT, look at the example — EVERY blasted one get’s “page_item”… if every single LI at the same level is getting the SAME class, they shouldn’t have a tag on them in the first place! that’s just common sense — something rarely if ever found in turdpress skins. Likewise if the things are nested you shouldn’t be needing to target them with garbage classes like “parent” and “child” EITHER! “current_page_parent” should be #menu li… “current_page_item” should be #menu li li… It’s not rocket science — except to the dumbasses who seem to throw classes on EVERYTHING for no fathomable reason apart from ineptitude. You know? Same type of ineptitude that has people still using javascript for rollovers like it was 1998? That same ineptitude that has people using 50k of markup and 100k of javascript for every 1k of plaintext?

        Craig already mentioned the IDIOCY of a title attribute redundant to the content — the only reason to use title is if the content of an element doesn’t convey it’s full meaning — If it’s IDENTICAL then it is serving no purpose other than wasting bandwidth.

        But of course I always say that if the content of an element conveys so little meaning you need a title attribute, you have the wrong content in that element!

        Which is why the only time I use TITLE is alongside ACCESSKEYs for things like Opera’s Accesskey menus.

  • garison54

    ds60 says: “current_page_parent should be #menu li… current_page_item should be #menu li li…”

    Which is true enough, on the surface. However, “#menu li” subsumes “#menu li li”. In other words, the styles you apply to your top level menu items are also applied to your sub-level items. This is fine, if you want cookie-cutter, everything-looks-the-same menus. If you want some flair to your menus, you need to be able to style each item individually. Yes, this bloats the markup, but doing otherwise bloats the CSS: each style defined in the top-level menu which you don’t want in a sub-menu has to be specifically turned off.

    Defining specific classes for your menu items prevents this. “#menu li” applies the given style to the entire menu tree; “#menu .top-level-item” (say) applies that style only to the items defined with that class.

    ds60 further says: “EVERY blasted one get’s (sic) page_item.”

    This is simply not true. Look at the example again. The “Home” item does not have any styles. A more robust example might show that only the Contact-Us portion of the tree contains those styles. This would imply that “page-item” is applied to that subsection of the menu which references the current page. (I admit that “page-item-#” is unnecessarily obscure. A better choice would be to use the menu text as a class name, such as “mi-contact-us”.)

    For the same reason, “current_page_ancestor”, “current_page_parent”, and “current_page_item” are not redundant. If the example is expanded to three or more sublevels, we would find that the first two classes are on distinct levels. These classes permit you to style the menu in a way that emulates breadcrumbs, making the path to the current page evident. For example, “page-item” could outline the entire section in red, “current_page_ancestor” could make the text a deep gray, “current_page_parent” could make it black, and “current_page_item” could show it in red. Understanding these classes and what they are used for will make your sites more interesting than if you just say “Ugh, I don’t like this” and remove them.

    • http://www.lunadesign.org awasson

      This was exactly what I was getting at but you’ve done a much better job of illustrating it.

    • http://xavisys.com aaroncampbell

      page_item is a class on all pages in the nav menu. However, you can also add posts, categories, tags, etc to menus. Those don’t get the page_item class.

      • garison54

        All the more reason to leave it as is. Robust style declaration allow you to create pages which look, well, more robust. If your intent is to create plain, black-and-white, cookie-cutter pages, then by all means, add the code to remove those classes. Otherwise, learn to use the styles WordPress provides.

      • http://www.optimalworks.net/ Craig Buckler

        If you intend using a class, then you can remove that replacement from the set of regexs. However, WordPress plays it safe and adds styles, titles and full URLs to every menu item. It’s unlikely you’ll need all that (especially the titles).

  • http://www.ehotelsinlondon.com/

    Thanks for the script, it was doing my head in having all those unnecessary ids and class names on the list items.

  • http://www.ankursharma.net Ankur

    Thats a great post :)

    i got the similar info in http://www.7tech.co.in/wordpress/how-to-add-a-wordpress-drop-down-menu-to-your-wordpress-theme/ too which is also good ;)

  • http://rhplus-studio.com.hk Aron

    This is sooo great! It always bordered me how page source looks after using build in menus.
    However after implementation on my site http://rhplus-studio.com.hk , it show error :
    Warning: preg_replace(): Unknown modifier ‘r’ in /…/…/…/rhplus-studio.com.hk/wp-content/themes/…/functions.php on line 43.
    Will try on localhost to see why.
    Anyway thanks for this solution.