Adding a sub-menu indicator to parent menu items

It’s very handy from a UI perspective to indicate which menu items have a sub-menu beneath them (often a small triangle or arrow is used). When using wp_nav_menu(), WordPress adds the “sub-menu” class to the <ul> tag of any sub-menus, but nothing to the parent list item; this makes them difficult to target.

We’ll start by adding the following to our functions.php file. Don’t forget to replace “themeslug” with, well, your own theme slug.

// Add CSS class to menus for submenu indicator

class Themeslug_Page_Navigation_Walker extends Walker_Nav_Menu {
    function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
        $id_field = $this->db_fields['id'];
        if ( !empty( $children_elements[ $element->$id_field ] ) ) {
            $element->classes[] = 'themeslug-menu-item-parent';
        }
        Walker_Nav_Menu::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
    }
}

Essentially, the above code is checking if the given menu item has children, and if so, append themeslug-menu-item-parent to the $classes array. For more information on what’s going on here (including walkers, a Core bug and other excitement), refer to the links at the end of this post.

For the above code to kick in, we need to tell menus to use it; this we’ll do with the walker argument when calling wp_nav_menu() (the following would go in a template file):

wp_nav_menu( array( 'walker' => new Themeslug_Page_Navigation_Walker ) );

Update July 9, 2012: See the comments below for the preferred method of filtering wp_nav_menu‘s arguments. Using the filter allows us to first make sure a menu is assigned, avoiding show-stopping errors if the menu is empty.

Now that we have our key class in place, we’ll use CSS and the content property to insert our triangle characters. Let’s add the following to style.css:

.themeslug-menu-item-parent > a:after {
    color: #ccc;
    content: 'a0 a0 25BC';
    font-size: 10px;
    vertical-align: 1px;
}

.sub-menu .themeslug-menu-item-parent > a:after {
    content: '\a0 \a0 \a0 \25B6';
}

The first rule adds some space and a down-pointing triangle to the anchor within our newly-identified menu item, while the second rule overrides the first with a right-pointing triangle for any parent menu items within sub-menus. We’re assuming a horizontal menu with dropdowns in this case; simply adjust the Unicode characters for your particular situation. Or replace them with arrows, floral hearts, airplanes… go nuts. Just remember that because we’re working in CSS, we need to escape the character’s code (just replace the “U+” with a backslash “\”).

Have fun!

Additional information

Walker class code: http://wordpress.stackexchange.com/questions/16818/add-has-children-class-to-parent-li-when-modifying-walker-nav-menu
The Walker class in the Codex: http://codex.wordpress.org/Function_Reference/Walker_Class
wp_nav_menu() in the Codex: http://codex.wordpress.org/Function_Reference/wp_nav_menu
CSS content property: http://www.w3.org/wiki/CSS/Properties/content
CSS :after pseudo-element: http://www.w3.org/wiki/CSS3/Selectors/pseudo-elements/:after
List of Unicode characters: http://en.wikipedia.org/wiki/List_of_Unicode_characters

Published by

Kirk Wight

I am a WordPress developer working at Automattic (the makers of WordPress.com). Based in Vancouver, BC, Canada, I am easily distracted and very curious.

19 thoughts on “Adding a sub-menu indicator to parent menu items”

  1. Awesome post and hardcore solution to what seems like a simple problem. Infinite bonus points for not using jQuery!

    It would be nice if this didn’t require editing the wp_nav_menu call. Couldn’t you intercept the walker on a hook or filter somewhere? Probably more complicated than your nice solution if you can.

    1. You’re absolutely right: we could use the wp_nav_menu_args filter instead of changing the wp_nav_menu() call, and it’s no more difficult (and certainly preferable if the hook is already in use in our functions.php).

      [php]/**
      * Set our new walker only if a menu is assigned,
      * and a child theme hasn’t modified it to one level deep
      */
      function themeslug_nav_menu_args( $args ) {
      if ( 1 !== $args[ ‘depth’ ] && has_nav_menu( ‘menu_location’ ) ) {
      $args[ ‘walker’ ] = new Themeslug_Page_Navigation_Walker;
      }
      return $args;
      }
      add_filter( ‘wp_nav_menu_args’, ‘themeslug_nav_menu_args’ );[/php]

  2. Hey Dying to use this…

    When you say: wp_nav_menu() (the following would go in a template file):

    where exactly do you mean in a template file? Which file? Does it matter which one? I’m using the Canvas theme from Woo, and have a Widget in the footer area which houses a Nav menu, and i want the arrows to show up next to the nav items that have a sub menu…..

    any help???
    thanks ,
    eric

  3. Life Saver. I’ve been searching for this exact type of solution and all I’ve got is jQuery hacks. +1

  4. Hey Kirk I have question.

    How is it possible to add a seperate css style to a filter menu.

    Example:

    This is a filter menu:like:http://www.quality-tuning.co.uk/ (see Quick Search (Cars))

    …..

    I can use the css:

    option:nth-child(1), option:nth-child(3) {
    background: #000;}

    Than every 1st and 3rd item has a background.
    I want in select_item_1595 the background:orange

    How must the css be?

    CS

    1. Sorry, I’ve no idea; I’ve never built a menu like that before. If you can get the name as an ID somehow, you could just target it directly with CSS.

  5. Thank you for this, i have also searched a lot without success so far. Unfortunately i cannot get it to run, the reason for sure simply that i am new into this. i copied your first code and directly below the one from the first comment into my childtheme functions.php, but there is no class added. I skipped using the second code of the post since as i understood it was replaced by the solution in the comment 😦 Anything i did wrong?

  6. Ah and by the way, i am also searching a lot for such a “leave a reply” solution as you have it here, is it a plugin that does the job or anything i could also use?

  7. I have a current workaround, although it has one bug.

    If the menu’s depth is set to greater than 1, for example 2, and the menu has 3 levels, then the second level parent, will still show the sub-menu class. As long as the user(s) editing the menu knows this, there won’t be a problem as they can control it.

    function my_parent_nav_class( $classes, $item, $args )
    {
    global $wpdb;
    $has_children = $wpdb -> get_var( “SELECT COUNT(meta_id) FROM {$wpdb->prefix}postmeta WHERE meta_key=’_menu_item_menu_item_parent’ AND meta_value=’” . $item->ID . “‘” );

    if ( $args->depth > 1 && $has_children > 0 )
    array_push( $classes, ‘sub-menu’ );

    return $classes;
    }
    add_filter( ‘nav_menu_css_class’, ‘my_parent_nav_class’, 10, 3 );

  8. There is another Workaround: The bug only is caused by the wrong args object getting passed to the page menu fallback and only occurs when there is no menu defined.
    So if we just check if there is a menu defined and only use the custom walker when we have a menu

    $walker = (wp_get_nav_menus(array(‘hide_empty’ => true))) ? new Themeslug_Page_Navigation_Walker : false;

    wp_nav_menu(array(
    ‘theme_location’ => ‘primary’,

    ‘walker’ => $walker
    ));

Comments are closed.