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

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

  1. Permalink  ⋅ Reply

    Jeremyclarke

    April 26, 2012 at 12:49pm

    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.

    • Permalink  ⋅ Reply

      kwight

      May 2, 2012 at 8:14am

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

      /**
       * 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' );
  2. Permalink  ⋅ Reply

    Eric Zentner

    April 26, 2012 at 2:26pm

    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. Permalink  ⋅ Reply

    Sebastian Crane

    October 25, 2012 at 1:28pm

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

  4. Permalink  ⋅ Reply

    kwight

    October 25, 2012 at 1:33pm

    Heheh, I’m not much for the jQuery hack approach to problem-solving either :)

  5. Permalink  ⋅ Reply

    c.bavota

    November 21, 2012 at 9:37am

    I’d been using some fancy CSS to get this working but the walker approach is so much better. Thanks Kirk.

    • Permalink  ⋅ Reply

      Kirk Wight

      November 21, 2012 at 10:14am

      Cool eh? Like @twigpress said, all that OOP stuff is pretty handy :)

  6. Permalink  ⋅ Reply

    Collin

    December 19, 2012 at 11:08am

    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

    • Permalink  ⋅ Reply

      Kirk Wight

      December 19, 2012 at 5:58pm

      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.

Leave a Reply