Creating a hierarchical submenu in WordPress

As I mentioned in Diving into WordPress I’ve started working with WordPress a bit. I’m getting the hang of it, but one thing that had me pulling my hair in frustration was creating a traditional hierarchical website submenu. I’m thinking of the kind of menu that normally appears in a sidebar when you’re in one of the sections of a horizontal top level menu.

There are multiple ways of creating menus in WordPress, but despite spending hours and hours searching the Web and testing plugins and code examples I couldn’t find a single example of a submenu that works the way I want it to (which happens to be the way submenus work in most other CMSs I have worked with).

So I ended up coding one myself.

Let’s say you have a website with several top level sections. Beneath one of those sections is the following page structure:

What I want is that the submenu starts out completely collapsed when you’re on the top level page (the parent of this sub-tree):

When “Page 1” is the active page, its siblings and immediate children are displayed:

Next, when “Sub page 1-2” is the active page, its parent (and the parent’s siblings), siblings and immediate children are displayed:

And so on, for however many levels deep the menu structure is.

I’ve found several ways of making WordPress render the entire page tree. That is not what I want. I also do not want the current page’s grandchildren. I want only the current sub-tree, and I want it to stop at the children of the current page.

Furthermore I do not want a solution that relies on CSS or JavaScript to hide the unwanted parts of the tree. For several reasons I want the HTML to reflect what is shown in the menu, nothing more.

Unless I’m missing something, both the wp_list_pages and wp_nav_menu functions appear incapable of doing this. It’s such a common way of rendering menus on CMS driven websites that I was a bit surprised when I couldn’t find any info on how to do it in WordPress.

Like I said, I ended up writing my own submenu function. It may contain bugs and conflict with PHP or WordPress best coding practices for all I know, but it outputs the HTML I want. So in case there are others going on the same WordPress menu hunt I did I thought I’d share what I came up with.

These two functions recursively create the menu:

function hierarchical_submenu($post) {
	$top_post = $post;
	// If the post has ancestors, get its ultimate parent and make that the top post
	if ($post->post_parent && $post->ancestors) {
		$top_post = get_post(end($post->ancestors));
	// Always start traversing from the top of the tree
	return hierarchical_submenu_get_children($top_post, $post);

function hierarchical_submenu_get_children($post, $current_page) {
	$menu = '';
	// Get all immediate children of this page
	$children = get_pages('child_of=' . $post->ID . '&parent=' . $post->ID . '&sort_column=menu_order&sort_order=ASC');
	if ($children) {
		$menu = "\n<ul>\n";
		foreach ($children as $child) {
			// If the child is the viewed page or one of its ancestors, highlight it
			if (in_array($child->ID, get_post_ancestors($current_page)) || ($child->ID == $current_page->ID)) {
				$menu .= '<li class="sel"><a href="' . get_permalink($child) . '" class="sel"><strong>' . $child->post_title . '</strong></a>';
			} else {
				$menu .= '<li><a href="' . get_permalink($child) . '">' . $child->post_title . '</a>';
			// If the page has children and is the viewed page or one of its ancestors, get its children
			if (get_children($child->ID) && (in_array($child->ID, get_post_ancestors($current_page)) || ($child->ID == $current_page->ID))) {
				$menu .= hierarchical_submenu_get_children($child, $current_page);
			$menu .= "</li>\n";
		$menu .= "</ul>\n";
	return $menu;

That code goes in your functions.php file. The hierarchical_submenu_get_children function calls itself recursively until it has listed any children of the currently viewed page.

To insert a menu on a page, call it like this:

<?php echo hierarchical_submenu($post); ?>

$post is a reference to the page currently being viewed.

Alternatively, if you want to do something special on pages that do not have a submenu, call the function like this:

$submenu = hierarchical_submenu($post);
if ($submenu) {
	echo $submenu;
} else {
	// Do something else

There has to be someone who has done this in WordPress before, but like I said I have not been able to find any descriptions of how to do it. Anyway, hopefully posting this will make it a bit easier for others who want to create a “normal” CMS-style submenu for a WordPress-driven site.

Update: For an alternative way of doing this, see Selective Page Hierarchy for wp_list_pages(), where Michael Fields explains how to extend the Walker_Page class. It’s a different approach that takes control of what wp_list_pages outputs rather than rewriting the functionality.

Posted on October 5, 2010 in WordPress, Content Management