Add Caret to Menu Items with Sub-Menus in WordPress Theme

I’m working on a custom theme using wp_nav_menu(). What I want to do is add a caret to menu items that have sub-menus. For example, If my menu looks like this:

  • Menu Item 1
  • Menu Item 2
    • Menu Item 2a
    • Menu Item 2b
  • Menu Item 3

I want to be able to format it like this:

Read More
  • List item
  • Menu Item 1
  • Menu Item 2 >
    • Menu Item 2a
    • Menu Item 2b
  • Menu Item 3

Without knowing the structure of the menu. This seems like a pretty common formatting problem, so I was wondering if there is any built-in functionality to provide for this.

Related posts

Leave a Reply

3 comments

  1. I do this using jQuery (since it doesn’t necessarily need to be in the TEXT (for screen readers, etc.) – just another option…:

    jQuery(document).ready(function() {
      jQuery('ul#nav li').has('ul').addClass('parentul');
    });
    

    Then for that “parentul” class I put in a background image of an arrow and position it to the right > …

  2. You can do this using a custom walker. Paste the following class at the bottom of your functions.php:

    class Nfr_Menu_Walker extends Walker_Nav_Menu{
    
                    /**
             * Traverse elements to create list from elements.
             *
             * Display one element if the element doesn't have any children otherwise,
             * display the element and its children. Will only traverse up to the max
             * depth and no ignore elements under that depth. It is possible to set the
             * max depth to include all depths, see walk() method.
             *
             * This method shouldn't be called directly, use the walk() method instead.
             *
             * @since 2.5.0
             *
             * @param object $element Data object
             * @param array $children_elements List of elements to continue traversing.
             * @param int $max_depth Max depth to traverse.
             * @param int $depth Depth of current element.
             * @param array $args
             * @param string $output Passed by reference. Used to append additional content.
             * @return null Null on failure with no changes to parameters.
             */
            function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
    
                    if ( !$element )
                            return;
    
                    $id_field = $this->db_fields['id'];
    
                    //display this element
                    if ( is_array( $args[0] ) )
                            $args[0]['has_children'] = ! empty( $children_elements[$element->$id_field] );
    
                    //Adds the 'parent' class to the current item if it has children               
                    if( ! empty( $children_elements[$element->$id_field] ) ) {
                            array_push($element->classes,'parent');
                            $element->title .= ' >';
                    }
    
                    $cb_args = array_merge( array(&$output, $element, $depth), $args);
    
                    call_user_func_array(array(&$this, 'start_el'), $cb_args);
    
                    $id = $element->$id_field;
    
                    // descend only when the depth is right and there are childrens for this element
                    if ( ($max_depth == 0 || $max_depth > $depth+1 ) && isset( $children_elements[$id]) ) {
    
                            foreach( $children_elements[ $id ] as $child ){
    
                                    if ( !isset($newlevel) ) {
                                            $newlevel = true;
                                            //start the child delimiter
                                            $cb_args = array_merge( array(&$output, $depth), $args);
                                            call_user_func_array(array(&$this, 'start_lvl'), $cb_args);
                                    }
                                    $this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output );
                            }
                            unset( $children_elements[ $id ] );
                    }
    
                    if ( isset($newlevel) && $newlevel ){
                            //end the child delimiter
                            $cb_args = array_merge( array(&$output, $depth), $args);
                            call_user_func_array(array(&$this, 'end_lvl'), $cb_args);
                    }
    
                    //end this element
                    $cb_args = array_merge( array(&$output, $element, $depth), $args);
                    call_user_func_array(array(&$this, 'end_el'), $cb_args);
            }
    }
    

    And then in header.php (or whever your wp_nav_menu is) do something like this:

    <?php
        $walker = new Nfr_Menu_Walker(); 
        wp_nav_menu( array( 'theme_location' => 'primary', 'walker' => $walker ) ); 
    ?>
    

    The key part of the walker is the following if statement:

    if( ! empty( $children_elements[$element->$id_field] ) ) {
                        array_push($element->classes,'parent');
                        $element->title .= ' &gt;';
    }
    

    This checks if the item has children, and if it does it adds the ‘parent’ css class to it, and then changes the title from ‘xxxx’ to ‘xxxx >’.

    (Adapted from http://wordpress.org/support/topic/wp_nav_menu-add-a-parent-class)

  3. These days it looks as if WordPress itself outputs a menu-item-has-children class on a menu li that has a ul.sub-menu.

    One can easily add styles to this to add a background image rather than having to use jQuery or the like.