Submenu navigation for category pages

Example of a menu on the wall of a diner. Example of a menu on the wall of a diner.
Photo by Sunrise Photos. The menu on the wall is a great example of menus in real life.

In WordPress you have two main taxonomies: categories and tags. I use categories as a taxonomy tree. That is why I want to show the submenu on the category page. It does not come out of the box, so I created something that renders the submenu items for me.

A screenshot of the Automation category showing the title, description, feature image and a list of sub menu links.
The Automation menu shows the submenu items on the category page.

Part 1: a method

Let's start with a method that generates the menu by calling wp_nav_menu. By default it generated the entire menu structure. But... we've added a sub_menu switch to the array. We'll use it to generate the sub menu.

function ktt_get_sub_menu($menu = 'Main Menu', $menu_class = "tax-sub-menu")
{
    return wp_nav_menu(array(
        'menu'        => $menu,
        'sub_menu'    => true,
        'echo'        => true,
        'items_wrap'  => '<ul class="' . esc_attr($menu_class) . '" class="%2$s">%3$s</ul>',
        'walker'         => '',
    ));
}

The output is pretty plain: a bunch of li and a elements. This makes styling easier.

Part 2: a hook

Let's hook into the wp_nav_menu_objects to filter the menu. We're only intereseted in the children of the current menu item. If the current page is not part of the menu, we'll return an empty array:

add_filter('wp_nav_menu_objects', 'ktt_sub_menu_items', 10, 2);


// filter_hook function to react on sub_menu flag
function ktt_sub_menu_items($sorted_menu_items, $args)
{
    if (!isset($args->sub_menu)) {
        return $sorted_menu_items;
    }

    $current_id = 0;

    // find the current menu item
    foreach ($sorted_menu_items as $menu_item) {
        if ($menu_item->current) {
            $current_id = $menu_item->ID;
            break;
        }
    }

    if ($current_id == 0) return [];

    // only use items that are direct children of the current
    foreach ($sorted_menu_items as $key => $item) {
        if ($item->menu_item_parent != $current_id) {
            unset($sorted_menu_items[$key]);
        }
    }

    return $sorted_menu_items;
}

Note: I use the same page for both tags and categories and it works for both. The reason it works, is that we based it on the menu and the menu is agnostic to which objects are underneath it 😁.

Usage

On my category page, I just echo the result after the description:

<?php if ('' != get_the_archive_description()) : ?>
    <div class="archive-meta">
        <?php the_archive_description(); ?>
    </div>
<?php endif;
echo ktt_get_sub_menu();

Shortcode?

But what if you wanted to use it in a widget or in a post (or only in certain places)? Well... you can turn it easily in a shortcode and use it wherever you want:

add_shortcode('highlights', 'ktt_sh_highlights');
function ktt_sh_sub_menu($atts)
{
    ob_start();
    ktt_get_sub_menu();
    $content = ob_get_contents();
    ob_end_clean();
    return $content;
}
expand_less