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

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

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

  4. 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);

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

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

      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.

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

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

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

Leave a Reply

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