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
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.
You’re absolutely right: we could use the
wp_nav_menu_args
filter instead of changing thewp_nav_menu()
call, and it’s no more difficult (and certainly preferable if the hook is already in use in ourfunctions.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]
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
NEVER MIND! Figured it out!
Life Saver. I’ve been searching for this exact type of solution and all I’ve got is jQuery hacks. +1
Heheh, I’m not much for the jQuery hack approach to problem-solving either 🙂
I’d been using some fancy CSS to get this working but the walker approach is so much better. Thanks Kirk.
Cool eh? Like @twigpress said, all that OOP stuff is pretty handy 🙂
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
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.
Thanks for posting this. Exactly what I needed for using this mobile menu extension: http://astuteo.com/mobilemenu
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?
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?
There is a bug in wordpress, that makes this solution obsolete:
http://core.trac.wordpress.org/ticket/24587
Someone has a workaround for this?
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 );
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
));
Exactly what I needed today! Thank you for saving me some time!