Goodbye, headaches. Hello, menus!

As a theme developer, most of the support questions I get are about configuring menus. If you’re also a theme developer, you have probably run into the same questions I have.

The trouble is that a large majority of users want custom menus and no two menus are the same. There are tons of plugins that fix this problem in their own way. Even some theme developers have integrated menus systems into their themes.

Unfortunately, all of these solutions are different. We haven’t had a standard to go by, at least until now. WordPress 3.0 will introduce a new navigation menu system. Sure, there are some limitations with this system. However, with a set standard, new plugins will likely emerge to fill in the gaps.

In this tutorial, I’ll walk you through the features of the new system and how to build this into your theme if you’re a developer.

What the menu system offers

In WordPress 3.0, you’ll gain another admin screen under your Appearance menu called Menus. In the screenshot below, I’ve highlighted four key elements of the page.

  • Theme Locations: If your theme supports nav menus, it'll register new locations for you to add your custom-created menus.
  • Individual Menus: This is where the names of your menus will appear after you've created them. (Note that the "+" sign will create a new menu.)
  • Add Menu Items: There'll be several meta boxes containing pages, other post types, categories, other taxonomies, and custom links to your menus.
  • Menu Items: Once you've added menu items to your menu, they will appear under the menu for your configuration.
WordPress Nav Menus

Each menu item can has its own configuration section too. Once the item has been added to the menu, you can open it and edit its attributes, which are standard things you might want to change about any menu.

  • URL
  • Navigation Label
  • Title Attribute
  • CSS Classes
  • Link Relationship (XFN)
  • Description
  • Link Target (Please note that a kitten dies every time a link is opened in a new window/tab.)
Nav Menu Item

Registering a menu for a theme (theme location)

To associate specific menus with locations within our themes, we need to register these locations. Otherwise, we won’t know which menu goes where. There are two functions we can use for this:

  • register_nav_menu(): Registers a single theme location.
  • register_nav_menus(): An array of locations we want to register.

In this example, we’ll register a single menu called Primary Menu from our theme’s functions.php file.

add_action( 'init', 'register_my_menu' );

function register_my_menu() {
	register_nav_menu( 'primary-menu', __( 'Primary Menu' ) );
}

primary-menu is the slug we’ll use to identify the menu in code. Primary Menu is the label we’ll use to identify the menu in the admin.

Building off that example, we’ll create multiple menus, which is not much different than registering a single menu.

add_action( 'init', 'register_my_menus' );

function register_my_menus() {
	register_nav_menus(
		array(
			'primary-menu' => __( 'Primary Menu' ),
			'secondary-menu' => __( 'Secondary Menu' ),
			'tertiary-menu' => __( 'Tertiary Menu' )
		)
	);
}

Displaying a nav menu

There are two ways to display a nav menu. One is by calling wp_nav_menu() within a theme template file. The other is by using the Navigation Menu widget.

Displaying a menu within a theme template

Most themes will call a menu from their header.php template, but menus can be placed anywhere. The simplest form of calling a nav menu in a theme will look like this:

<?php wp_nav_menu(); ?>

That will load the first menu the user created or fall back on a standard page list if no menus exist.

We want to have a bit more control than that though. Let’s call our menu from the previous section (Primary Menu).

<?php wp_nav_menu( array( 'theme_location' => 'primary-menu' ) ); ?>

You’re allowed even more control. wp_nav_menu() has several parameters you can use when displaying a menu.

  • theme_location: The menu to call that is associated with the specific theme location.
  • menu: Call a specific menu ID, slug, or name.
  • container: The element that wraps around the list. The default is div but can be changed to nav if you've moved on to HTML 5.
  • container_class: The CSS class of the container.
  • menu_class: The CSS class given to the unordered list. This defaults to menu.
  • fallback_cb: A function to call in the event that no menu items have been given. By default, wp_list_pages() will be called.
  • before: Text that is displayed before the link text but within the link.
  • after: Text that is displayed after the link text but within the link.
  • link_before: Text that is displayed before the link.
  • link_after: Text that is displayed after the link.
  • depth: How many levels the menu should display, which is useful for things like drop-down menus. This is set to 0 (any level) by default.
  • walker: Allows a custom walker PHP class to be defined to create the menu.
  • echo: Whether to display the menu or return it for use in PHP. This defaults to true and displays the menu.

Displaying a menu using the Navigation Menu widget

By default, WordPress will give you a simple menu widget that will allow you to select any of your custom menus to display. All you need is a widget-ready theme.

Since using widgets is fairly self-explanatory and the new widget is simple, I’m going to use this opportunity for a little shameless self promotion.

For those of you that use my Hybrid theme, you know you have the coolest widgets ever because they give you complete control. Version 0.8 will have a much more advanced Navigation Menu widget that overwrites the WordPress default. Here’s a look at what controls you’ll have.

Navigation Menu Widget

Styling nav menus

I won’t be covering how to style menus here. That’s a tutorial in and of itself, and there are tons of different ways to style menus. I do have a couple of tips though.

Use the current-menu-item class to style a menu item whose page is currently being viewed. This will allow you to highlight the item so the reader will know which page they’re on. Here’s an example from one of my style.css files:

#primary-menu li.current-menu-item a {
	background: #fff url(images/primary-menu-active.png) repeat-x 0 0;
	border-top: none;
	border-bottom: 2px solid #fff;
	}

A solid base to start with is the Superfish jQuery plugin. It allows some subtle, but cool, JavaScript functionality and fixes drop-downs in Internet Explorer 6 without having to resort to hacks.

Here’s a screenshot of a menu I’ve been working on for a new theme.

Navigation menu frontend

At this point, you should know everything you need to know about handling menus in WordPress. But, if you continue reading, I have a few more tips that you can use.

Collapsible menus

Let’s suppose you only want a menu to appear when a user has added menu items. This can allow for a variety of layouts. In the screenshot below, I’m using two menus. But, this allows for up to four different layouts.

Collapsible menus

The trick here is to make sure there’s no fallback called. Let’s return to our original menu (Primary Menu) and the code for it.

<?php wp_nav_menu( array( 'theme_location' => 'primary-menu', 'fallback_cb' => '' ) ); ?>

What I’ve done is simply tell WordPress that I don’t want my menu to fall back to another menu if the user hasn’t given the menu any items.

Checking if a theme location has a menu

Update: Props to Andrew Nacin for adding the has_nav_menu() function after reading this section of the article.

WordPress currently has no conditional tag to check if a menu has been set to a specific theme location. WordPress has a conditional tag called has_nav_menu() to check if a menu has been set to a specific theme location. Let’s suppose we’re creating a container with a menu and a search form. But, if no menu is set for the theme location, we don’t want either to appear.

In the screenshot, you can see that neither appear in one scenario and both appear in the other.

Collapsible menu container

When you call your menu by theme location, you can first check to see if a menu is associated with the location. Note that we’re checking the theme location slug and not a menu name or ID.

<?php if ( has_nav_menu( 'primary-menu' ) ) { ?>

	<div class="nav-container">
		<?php wp_nav_menu( array( 'theme_location' => 'primary-menu' ) ); ?>
		<?php get_search_form(); ?>
	</div>

} ?>

Allowing more menu containers

The menu system will only allow for

and