Building a Dynamic Secondary Menu

I recently redeveloped the Texas 4-H website (go ahead, check it out!), and one of the unique features is its dynamic secondary menu.

This website has lots of deep content; most of its content is three levels deep. We added a secondary menu both to indicate the user’s current location in the site and to simplify browsing these sub-sections. I didn’t want the client managing two separate menus since they would invariably get out of sync when someone updates the primary menu but forgets to update the secondary menu.

Creating a Dynamic Secondary Menu

I created one menu and assigned it to both the ‘header’ and ‘secondary’ theme locations. The code below only affects the ‘secondary’ theme location. It first finds the active section by looking for a class of ‘current-menu-item’ or ‘current-menu-ancestor’ on one of the top level menu items. If no active section is found, it returns false, which removes the secondary menu. If an active section is found, it gathers all the menu items that are within that section and displays them in the secondary menu.

This is somewhat similar to my plugin Genesis Subpages as Secondary Menu. The difference is this code below generates the secondary menu based on the submenu items (managed in Appearance > Menus). That plugin generates the submenu based on page items (managed in Pages).

The benefit to this approach is you can include non-pages in the menu and the secondary menu will still work just fine. In this case, “Projects” is a custom post type, the submenu items are terms in the “project category” taxonomy, and the third level items are posts in the “projects” post type. This code will also ensure your secondary menu is “in sync” with the primary menu. So if you exclude a subpage from the menu, it won’t appear in the secondary menu.

<?php
/**
* Submenu items in secondary menu
*
* Assign the same menu to 'header' and 'secondary'.
* This will display the current section's subpages in 'secondary'
*
* @author Bill Erickson
* @link http://www.billerickson.net/building-dynamic-secondary-menu
*
* @param array $menu_items, menu items in this menu
* @param array $args, arguments passed to wp_nav_menu()
* @return array $menu_items
*
*/
function be_submenu_items_in_secondary( $menu_items, $args ) {
// Only run on 'secondary' menu location.
if( 'secondary' !== $args->theme_location )
return $menu_items;
// Find active section
$active_section = false;
foreach( $menu_items as $menu_item ) {
if( ! $menu_item->menu_item_parent && array_intersect( array( 'current-menu-item', 'current-menu-ancestor' ), $menu_item->classes ) )
$active_section = $menu_item->ID;
}
if( ! $active_section )
return false;
// Gather all menu items in this section
$sub_menu = array();
$section_ids = array( $active_section );
foreach( $menu_items as $menu_item ) {
if( in_array( $menu_item->menu_item_parent, $section_ids ) ) {
$sub_menu[] = $menu_item;
$section_ids[] = $menu_item->ID;
}
}
return $sub_menu;
}
add_filter( 'wp_nav_menu_objects', 'be_submenu_items_in_secondary', 10, 2 );
view raw functions.php hosted with ❤ by GitHub

WordPress Development

Bill Erickson

Bill Erickson is a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments

    • Bill Erickson says

      Actually I think the issue was I forgot to define $sub_menu, so if there weren’t any submenu items there wasn’t a $sub_menu variable to return.

      I’ve updated the code snippet to fix this.

    • Bill Erickson says

      It would be simpler to use my BE Subpages Widget plugin. But that would be based on the section’s subpages, not using the menu sub-items (like this tutorial does).

      If you really needed it based on menus, create a custom widget and add a nav menu to it using a theme_location you’ve defined. Then style it to look the way you’d like.

  1. Patrick Boehner says

    Hi Bill,

    Fantastic tutorial, i will have to dig into this one. Just noticed a mistake in the write up that was was causing some confusion. For your project custom post type taxonomy you call it “product category”, i assume this is “project category” and not a permalink rewrite on the site.

  2. Christina says

    Thank you VERY much for this. I have put it in a sidebar and it works great, the only thing I need to figure out and I’m coming up empty is to get the .current-menu-ancestor so I can put it before the list of menu items. In jQuery it’s this, which is my quick fix, but if it’s not too much trouble, can you update your example? Thanks!

    var str = $(‘.nav-primary .current-menu-ancestor > a’).text();
    $( ‘nav.nav-secondary’ ).prepend(str);

  3. Christina says

    Actually, I only want the top parent not any children, so if I’m on a grand-child link, I just want the grandparent and so on.

    var str = $(‘.nav-primary > ul > .current-menu-ancestor > a’).text();

  4. Jamaluddin Rahmat says

    Great tutorial as always Bill 🙂

    This questions may be out of topic.
    How you can make a menu description like ‘Volunteer – give your time’?
    Make with genesis nav hook/filter or via menus in WP admin?

    Thank for your answers 🙂

  5. Johnny Chandler says

    Hello Bill, thanks for all that you do! I do have a question! I want to add sub menu items to my secondary menu in genesis! It seems difficult to do and I am not having much luck finding anything that works. There are several reasons i want to do, one is- I want to keep the primary up top and have the search form inside it! I really dont have any sub pages to list in it, they are located on the secondary menu.
    I can swap the locations to make it work, but i would rather keep it simple. Is there any functions code that I can add this, i know I would have to add the css for it. Would you mind pointing me in the right direction?
    You rock…
    Johnny C

    • Bill Erickson says

      What do you need done that the above code doesn’t do? It sounds like you want the primary menu to not display subpages even though they are used in the secondary menu. In that case, use this tutorial to limit the depth of the primary menu to 1. Even if you add submenus and sub-submenus, they won’t appear in the primary menu if the depth is set to 1.

  6. Hans Schuijff says

    Hi Bill,

    I like the possibilities this gives, thanks for sharing and explaining.

    Looking at the experience in the website you build, I would not directly have understood what is happening with the menu’s as a visitor. There seems a lot going on, a bit overwhelming at first. If the first menu would become a one level menu (not show sub-items) after the second menu is visible, than it would become a bit less busy for me. Perhaps then it could be presented more as if the second menu is just the opening of the second level of the first menu, visually making the second menu sort of part of the first one. Having the same menu-items open in the second menu and still available in the first menu-is a bit much for me. But I understand that’s a design choice.

    I’m curious though, is it easy to show the first menu as a one-level menu when the second one is visible showing the second level menu-items? Perhaps I will try that at some point. I was thinking of changing the display of my sub-menu’s to a more horizontal display using a second bar, so perhaps this is the key that I was searching for.

    I really like the presentation of the mobile menu on your own website by the way. Just one menu-level linking to archives will probably be the cleanest presentation for a website with a lot of content. I decided to make it like that (no sub-menu-items) on another website, after noticing the menu’s grew bigger and bigger and the screensizes didn’t grow with it. It’s more clean that way.

    • Bill Erickson says

      I agree it’s not the easiest menu to use, but I understand why the designer chose to do it that way. The top level items are actually categories, and almost all of the traffic is to the secondary menu items. By providing the dropdown in the primary menu, users can get to those pages without visiting an intermediary page.

      If you wanted to limit the primary menu to only top level items but keep the same functionality in this post (sub-menu and sub-sub menu items appear in secondary menu), use the wp_nav_menu_args filter to limit the depth of ‘primary’ to 1. See this post for more information.

      • Hans Schuijff says

        Yes, I understood that about the availability of the submenu-items when menu2 is not shown. But after the secondary menu is shown, there is no need to also keep the same items in the first. But as you say, It’s a designers choice obviously.

        Thanks for the tip on making the primary menu only one level. I’ve seen something similar in the footer menu of one of the genesis themes I used earlier. I’ll give it a go later on.

  7. Albert says

    Hi Bill,

    you’ve mentioned that ‘submenu items are terms in the “project category” taxonomy’.

    Could you give me a hint of how this could be modified so that the submenu items could be terms in a taxonomy that have a specific term meta key?
    I mean key (not value) because meta keys are indexed and, since my possible values are binary (true or false), I think this should be faster.

    Thanks

    • Bill Erickson says

      The above tutorial uses an existing menu as the data source. It is not dynamically building the submenu.

      So if you were to build it in the above described way, simply make sure the submenu items in your primary menu are configured the way you want (ie: everything with a meta_key matching what you want is added to the appropriate submenu) and then the dynamic secondary menu will automatically show the right ones.

      Now if you want to dynamically build a menu with terms with a specific meta key, you could try passing that meta query to get_terms() but I’m not sure if that will work (I’ve never done it before). Alternatively, run get_terms() to retrieve all terms in that taxonomy, and then loop through the results, checking the $term['meta'] parameter before including it in your menu.

Leave A Reply