WordPress Navigation Menus: Clean-up Classes and Add Page Names as IDs

I would have thought this would be a lot easier than it initially was, and in the end, it is really simple, but it took some time to figure it out, so I thought I’d pass along.

Innately WP creates menus that look something like this:

<ul id="menu-top-nav" class="menu">
<li id="menu-item-72" class="menu-item menu-item-type-post_type current-menu-item page_item page-item-58 current_page_item menu-item-72"><a href="about-us">About Us</a></li>
<li id="menu-item-73" class="menu-item menu-item-type-post_type current-menu-item page_item page-item-59 current_page_item menu-item-73"><a href="contact-us">Contact Us</a></li>
<li id="menu-item-74" class="menu-item menu-item-type-post_type current-menu-item page_item page-item-60 current_page_item menu-item-74"><a href="services">Services</a></li>
</ul>

I don’t know about you, but to me, that is not only bloated and ugly, it is also pretty unusable… For anyone that works in both a Development and Production environment, or has ever had to move a WP installation, those numbers will change, making any CSS really hard to write, let along readable/understandable…

What I wanted was something more like:

<ul id="menu-top-nav" class="menu">
<li id="nav-about-us" class="current_page_item"><a href="about-us">About Us</a></li>
<li id="nav-contact-us" class=""><a href="contact-us">Contact Us</a></li>
<li id="nav-services" class=""><a href="services">Services</a></li>
</ul>

Notice how the ID for each <li> is now the page slug, prefaced with nav-? Not that’s useful! I did keep the current_page_item class as that’s really useful too, but otherwise, dump that fluff!

In the end, all I needed were two very small functions, implemented via a filter:

// Reduce nav classes, leaving only 'current-menu-item'
function nav_class_filter( $var ) {
return is_array($var) ? array_intersect($var, array('current-menu-item')) : '';
}
add_filter('nav_menu_css_class', 'nav_class_filter', 100, 1);

// Add page slug as nav IDs
function nav_id_filter( $id, $item ) {
return 'nav-'.cleanname($item->title);
}
add_filter( 'nav_menu_item_id', 'nav_id_filter', 10, 2 );

The nav_id_filter function makes use of a function I use pretty regularly, to create “codeable” versions of text:

function cleanname($v) {
$v = preg_replace('/[^a-zA-Z0-9s]/', '', $v);
$v = str_replace(' ', '-', $v);
$v = strtolower($v);
return $v;
}

This just removes anything that isn’t alpha-numeric, converts spaces to dashes, and makes everything lowercase; basically converts any string into a “slug”. Probably a better way to write that, but it works…

I’ll likely push this as a Plug-in, again, because it seems really useful, but wanted to make it available to anyone else trying to do the same…

Cheers,
Atg

30 Responses to WordPress Navigation Menus: Clean-up Classes and Add Page Names as IDs

  1. aarontgrogg says:

    And it appears that the hooks I finally found are only called on custom menus…

    Will try to find hooks that are always called and update this…

    Thanks, WP! :-)

    Atg

  2. Piet says:

    This looks great indeed! Any idea on how to implement this for the “common” wp_list_pages() ?

  3. Fask says:

    fantastic solution, thanks for sharing

  4. Nico says:

    Awesome! Is it possible to remove empty classes?
    Services
    instead
    <li id="xxx" class="">Services

    Thanks!

  5. aarontgrogg says:

    @Piet: Planning to try to find that, will update this Post if/when I do…

    @ Fask: Thanks!

    @Nico: Definitely possible, but likely would involve editing the actual WP code, whereas the above can be done via a Theme’s function.php. Will take a look though, updating here if I find a way.

    Atg

  6. Eric Guess says:

    Thanks for posting this Aaron. As a front-end guy that doesn’t touch all that much PHP, this came in very handy!

  7. aarontgrogg says:

    Just letting everyone know that I have converted this code into a WordPress plug-in, if you’re interested.

    Atg

  8. Jason Wong says:

    Hi Aaron,

    Overall, this plugin is amazing as it cleans up most of the IDs and Classes. Unfortunately, it removes some key Menu Nav classes that are essential for Menu highlighting. Especially, I want the Parent Menu Nav to highlight when its Child Page/Post is selected and currently shown. With this, the needed Classes are:

    current-menu-item
    current-menu-parent
    current-menu-ancestor

    I hope that you can exclude these Classes in your next release.

    Cheers,
    Jason

    • aarontgrogg says:

      @Jason:

      current-menu-item (or something similar) should be retained, but, yes, a future enhancement would be to have an admin page with all of the native WP classes listed, allowing the developer to check what they want to keep, uncheck the ones they don’t.

      One of these days… :-)

      Thx for the comment!
      Atg

  9. chris says:

    You can add the current-menu-parent and current-menu-ancestor to the array

    function nav_class_filter( $var ) {
    return is_array($var) ? array_intersect($var, array('current-menu-item', 'current-menu-parent', 'current-menu-ancestor')) : '';
    }
    add_filter('nav_menu_css_class', 'nav_class_filter', 100, 1);

  10. aarontgrogg says:

    @Chris: Thanks, yes, I should have pointed out in my reply to @Jason… :-)

    Cheers,
    Atg

  11. eShinn says:

    Ugh!! You are my hero for the day!!!

  12. Derek says:

    Grand! Very much appreciated, looks so much cleaner already :)

  13. Sam Alexander says:

    WordPress also comes with a built-in function to generate proper slugs from strings:

    See:

    http://codex.wordpress.org/Function_Reference/sanitize_title_with_dashes

  14. aarontgrogg says:

    @Sam: Thanks! I assume that’s in reference to my cleanname function? Cool, not crazy that it doesn’t deal with “special accented characters”, though neither does mine, but then, someone could always edit mine if they needed to. Bah. In any case, thanks!

  15. trmash says:

    Sorry to dig up an old post, but I’m just wondering how to adapt this code to work with two navigations? For example, a header and footer.

    In its current form, a website ends up with duplicate IDs if there is more than once instance of a navigation with similar links (even if it is a different menu). Is there a way we can tell it to change ‘nav-‘ to ‘nav2-‘, ‘nav3-‘ and so on?

    Thanks!

    • aarontgrogg says:

      @trmash: Yes, a known issue, and one I will try to address on the next iteration.

      Thanks,
      Atg

    • aarontgrogg says:

      So, the issue with numerating the navigation menus is two fold:

      1. What if a page has a top and bottom menu (so they would be nav-1 and nav-2), but then another page, in addtion to those, also has a new middle nav; this middle nav would not get nav-2 and the bottom would become nav-3
      2. The individual menus are actually not aware of each other, so it would be hard to innumerate them, without reverting to something like session variables or wp_options that would have to be consulted & updated on each nav build…

      It could be that simply removing the ID would be the way to go… Still thinking…

      Atg

      • trmash says:

        A gentle prod… ;)

        Actually, I was just wondering how I might be able to go about stripping the ID from the second menu – is this easily done, or a whole rewrite?

        Appreciate your work, thanks.

  16. aarontgrogg says:

    @trmash: gentle prod sheepishly accepted… :-)

    to remove would not be hard, but at this point i would be afraid of breaking CSS that someone already has in place. but, still probably the right way to go. not a complete rewrite, but will require a function edit.

  17. eric mueller says:

    brilliant stuff– exactly what I needed. surprised WP doesn’t do this by default. thanks for your contribution. E

  18. John says:

    Why is it necessary to check is_array($var)? Can you do without it?

    • aarontgrogg says:

      @John: You have to make sure $var is an array before you can use array_intersect. If you pass a non-array, it will throw an error.

      • John says:

        Isn’t it always an array though?

        • aarontgrogg says:

          erm, I don’t remember off the top of my head, but I’m going to guess that I must have encountered an error at some point that made me do that, but in either case, it is a good practice, and really doesn’t have a downside, aside from one very quick test, so, I’d stick with it…

          you see any major issues with having it there?

          • John says:

            I saw no issues leaving it out, so I was curious why it was there. But if something screws up I’ll come back and let you know :)

  19. Mar says:

    Thank you very much! Useful

  20. Nice approach, however using the itmes title in the id construction, will change the id if the current administrator intercepts with the menu title in menu settings.

    Why not using the page id instead?

    To add the menu slug will also create an unique id for the same page. Add a third arguments that actually contains the menu information.

    add_filter(‘nav_menu_item_id’, ‘nav_id_filter’, 10, 3 );

    = function nav_id_filter($id, $item, $args)

    and

    return ‘nav-‘.$args->menu->slug.’-‘.$item->object_id;

    This is what we use:

    function nav_class_filter( $var ) {
    if(!is_array($var)) return $var;
    $pattern = array(
    ‘current_page_item’,
    ‘current_page_parent’,
    ‘current_page_ancestor’,
    ‘current-cat’,
    ‘current-menu-item’,
    ‘current-menu-parent’,
    ‘current-menu-ancestor’
    );
    if(!empty(array_intersect($var, $pattern))) return array(‘selected’, ‘selected-onload’);
    else return ”;
    }
    add_filter(‘nav_menu_css_class’, ‘nav_class_filter’, 100, 1);

    function nav_id_filter($id, $item, $args) {
    return ‘nav-‘.$args->menu->slug.’-‘.$item->object_id;
    }
    add_filter(‘nav_menu_item_id’, ‘nav_id_filter’, 10, 3 );

    Hope this comment doesnt end up to cluttered, I cant find any allowed tags information.

    / Jonas Lundman, Intervik development

    • Thanks for the note, Jonas.

      I guess the part I was trying to avoid was having to know the page’s ID when working in CSS.

      Your method doesn’t seem that different to the native WP behavior, where you have to know the menu-item ID…

      But, which ever works best for you, is the best method! :-)

      Cheers,
      Atg

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.