WordPress Menus – differences between Nav and Page Menus

I have recently been designing a WordPress theme which needs to support theme users who want to design a menu using the WordPress Appearance->Menus admin page (known as a Nav Menu), but also supporting theme users who want the default automatically generated menu that uses the page hierarchy (known as a Page Menu).

The standard way to do this is to incorporate a call to wp_nav_menu in the theme. This in principle does everything I want – it checks for a Nav Menu and if it exists, uses that. Otherwise it calls a fallback function which is set by default to be the wp_page_menu function that automatically generates a Page Menu.

The function call includes a parameter to select which Nav Menu to use and other arguments to control the menu generation. In this case the theme only handles two levels of menu:

wp_nav_menu(array('theme_location' => 'primary', 'depth' => 2));

The problem is that the two menu generators create quite different HTML by default. Yet I want menus to look the same in my theme regardless of which option the them user has chosen.

Here’s the default output of wp_nav_menu if there is a Nav Menu to generate:

<div class="menu-navigation-container">
  <ul id="menu-navigation" class="menu">
    <li id="menu-item-67" class="menu-item menu-item-type-post_type menu-item-object-page current-menu-item page_item page-item-5 current_page_item menu-item-67"><a href="http://localhost/wordpress-themes/wordpress/">Home</a></li>
    <li id="menu-item-68" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-68"><a href="http://localhost/wordpress-themes/wordpress/?page_id=7">Menu</a>
      <ul class="sub-menu">
	<li id="menu-item-69" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-69"><a href="http://localhost/wordpress-themes/wordpress/?page_id=18">Submenu</a></li>
      </ul>
    </li>
  </ul>
</div>

Compare this with the output of wp_page_menu:

<div class="menu">
  <ul>
    <li class="page_item page-item-5 current_page_item"><a href="http://localhost/wordpress-themes/wordpress/">Home</a></li>
    <li class="page_item page-item-7"><a href="http://localhost/wordpress-themes/wordpress/?page_id=7">Menu</a>
      <ul class='children'>
        <li class="page_item page-item-18"><a href="http://localhost/wordpress-themes/wordpress/?page_id=18">Submenu</a></li>
      </ul>
    </li>
  </ul>
</div>

There are some strange inconsistencies here between the Nav Menu and the Page Menu:

  • In the Nav Menu the first item is classed as a ‘page-item’ but none of the other entries is, even though they are all pages. This seems to me to be a bug, especially when you see that the class ‘menu-item-68’ has been repeated.
  • In the Page Menu all items are classed ‘page-item’.
  • However, in the Nav menu, all items are classed ‘menu-item’.
  • In the Nav Menu the class ‘menu’ is attached to the ul element. In the Page Menu its on the div.
  • In the Nav Menu, Submenus are class ‘sub-menu’, whilst in the Page Menu the class is ‘children’.

So, I want to normalise the two outputs, at least a bit, in order to make the task of having a single CSS definition for the menu design that works on both possible outputs.

First, I note that there are fewer options on wp_page_menu than wp_nav_menu so I’m going to start by adjusting the Nav Menu to be closer to the Page Menu. The most important change is to have a consistent starting point for CSS styles, in the form of an overall ID or class on the enclosing tag which I’ll do by attaching a class to the div in both cases. I will also change the class to ‘site-menu’ to make it more specific. I would prefer an ID, but that isn’t easy to add to a Page Menu.

Some of this can be done by adding options to the wp_nav_menu call:

wp_nav_menu(array('theme_location' => 'primary', 'depth' => 2, 'container_class' => 'site-menu', 'menu_class' => ''));

This removes the class ‘menu’ from the top-level ul tag and adds it to the container div instead.

The problem then is that, if wp_nav_menu can’t find a Nav Menu, it calls wp_page_menu and passes all its parameters to it. In the definition of wp_page_menu, the menu_class parameter is used to add the class to the enclosing div, which has now been removed.

The solution is to stop the fallback from happening and take control of the two menu generators separately, using the has_nav_menu function to determine which to call.

  if (has_nav_menu('primary'))
  {
    // Use a Navigation menu
    wp_nav_menu(array('theme_location' => 'primary', 'depth' => 2,
'container_class' => 'site-menu', 'menu_class' => '', 'fallback_cb' => false));
}
  else
  {
    // Use a Page menu
    wp_page_menu(array('depth' => 2, 'menu_class' => 'site-menu'));
  }

Now, whichever way the menu is generated, the ‘site-menu’ class will be on the div element.

Then, in the CSS, I can style either menu in the same way using the following selectors:

.site-menu>ul
.site-menu>ul>li
.site-menu>ul>li>a
.site-menu>ul>li>ul
...

An alternative approach is to put a wrapper element around the whole menu of course:

<nav id="site-navigation" role="navigation">
<?php /* code for generating the menus */ ?>
</nav>

In this case the CSS selectors become:

#site-navigation>div>ul
#site-navigation>div>ul>li
#site-navigation>div>ul>li>a
#site-navigation>div>ul>li>ul
...

However, there are other reasons for separating out the Nav Menu handling and Page Menu handling, which I will cover in the next article.

Leave a Reply