Displays a navigation menu.
Parameters
$args
arrayoptional- Array of nav menu arguments.
menu
int|string|WP_TermDesired menu. Accepts a menu ID, slug, name, or object.
menu_class
stringCSS class to use for the ul element which forms the menu.
Default'menu'
.menu_id
stringThe ID that is applied to the ul element which forms the menu.
Default is the menu slug, incremented.container
stringWhether to wrap the ul, and what to wrap it with.
Default'div'
.container_class
stringClass that is applied to the container.
Default ‘menu-{menu slug}-container’.container_id
stringThe ID that is applied to the container.container_aria_label
stringThe aria-label attribute that is applied to the container when it’s a nav element.fallback_cb
callable|falseIf the menu doesn’t exist, a callback function will fire.
Default is'wp_page_menu'
. Set to false for no fallback.before
stringText before the link markup.after
stringText after the link markup.link_before
stringText before the link text.link_after
stringText after the link text.echo
boolWhether to echo the menu or return it. Default true.depth
intHow many levels of the hierarchy are to be included.
0 means all. Default 0.
Default 0.walker
objectInstance of a custom walker class.theme_location
stringTheme location to be used. Must be registered with register_nav_menu() in order to be selectable by the user.items_wrap
stringHow the list items should be wrapped. Uses printf() format with numbered placeholders. Default is a ul with an id and class.item_spacing
stringWhether to preserve whitespace within the menu’s HTML.
Accepts'preserve'
or'discard'
. Default'preserve'
.
Default:
array()
Source
function wp_nav_menu( $args = array() ) {
static $menu_id_slugs = array();
$defaults = array(
'menu' => '',
'container' => 'div',
'container_class' => '',
'container_id' => '',
'container_aria_label' => '',
'menu_class' => 'menu',
'menu_id' => '',
'echo' => true,
'fallback_cb' => 'wp_page_menu',
'before' => '',
'after' => '',
'link_before' => '',
'link_after' => '',
'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>',
'item_spacing' => 'preserve',
'depth' => 0,
'walker' => '',
'theme_location' => '',
);
$args = wp_parse_args( $args, $defaults );
if ( ! in_array( $args['item_spacing'], array( 'preserve', 'discard' ), true ) ) {
// Invalid value, fall back to default.
$args['item_spacing'] = $defaults['item_spacing'];
}
/**
* Filters the arguments used to display a navigation menu.
*
* @since 3.0.0
*
* @see wp_nav_menu()
*
* @param array $args Array of wp_nav_menu() arguments.
*/
$args = apply_filters( 'wp_nav_menu_args', $args );
$args = (object) $args;
/**
* Filters whether to short-circuit the wp_nav_menu() output.
*
* Returning a non-null value from the filter will short-circuit wp_nav_menu(),
* echoing that value if $args->echo is true, returning that value otherwise.
*
* @since 3.9.0
*
* @see wp_nav_menu()
*
* @param string|null $output Nav menu output to short-circuit with. Default null.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$nav_menu = apply_filters( 'pre_wp_nav_menu', null, $args );
if ( null !== $nav_menu ) {
if ( $args->echo ) {
echo $nav_menu;
return;
}
return $nav_menu;
}
// Get the nav menu based on the requested menu.
$menu = wp_get_nav_menu_object( $args->menu );
// Get the nav menu based on the theme_location.
$locations = get_nav_menu_locations();
if ( ! $menu && $args->theme_location && $locations && isset( $locations[ $args->theme_location ] ) ) {
$menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] );
}
// Get the first menu that has items if we still can't find a menu.
if ( ! $menu && ! $args->theme_location ) {
$menus = wp_get_nav_menus();
foreach ( $menus as $menu_maybe ) {
$menu_items = wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) );
if ( $menu_items ) {
$menu = $menu_maybe;
break;
}
}
}
if ( empty( $args->menu ) ) {
$args->menu = $menu;
}
// If the menu exists, get its items.
if ( $menu && ! is_wp_error( $menu ) && ! isset( $menu_items ) ) {
$menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
}
/*
* If no menu was found:
* - Fall back (if one was specified), or bail.
*
* If no menu items were found:
* - Fall back, but only if no theme location was specified.
* - Otherwise, bail.
*/
if ( ( ! $menu || is_wp_error( $menu ) || ( isset( $menu_items ) && empty( $menu_items ) && ! $args->theme_location ) )
&& isset( $args->fallback_cb ) && $args->fallback_cb && is_callable( $args->fallback_cb ) ) {
return call_user_func( $args->fallback_cb, (array) $args );
}
if ( ! $menu || is_wp_error( $menu ) ) {
return false;
}
$nav_menu = '';
$items = '';
$show_container = false;
if ( $args->container ) {
/**
* Filters the list of HTML tags that are valid for use as menu containers.
*
* @since 3.0.0
*
* @param string[] $tags The acceptable HTML tags for use as menu containers.
* Default is array containing 'div' and 'nav'.
*/
$allowed_tags = apply_filters( 'wp_nav_menu_container_allowedtags', array( 'div', 'nav' ) );
if ( is_string( $args->container ) && in_array( $args->container, $allowed_tags, true ) ) {
$show_container = true;
$class = $args->container_class ? ' class="' . esc_attr( $args->container_class ) . '"' : ' class="menu-' . $menu->slug . '-container"';
$id = $args->container_id ? ' id="' . esc_attr( $args->container_id ) . '"' : '';
$aria_label = ( 'nav' === $args->container && $args->container_aria_label ) ? ' aria-label="' . esc_attr( $args->container_aria_label ) . '"' : '';
$nav_menu .= '<' . $args->container . $id . $class . $aria_label . '>';
}
}
// Set up the $menu_item variables.
_wp_menu_item_classes_by_context( $menu_items );
$sorted_menu_items = array();
$menu_items_with_children = array();
foreach ( (array) $menu_items as $menu_item ) {
/*
* Fix invalid `menu_item_parent`. See: https://core.trac.wordpress.org/ticket/56926.
* Compare as strings. Plugins may change the ID to a string.
*/
if ( (string) $menu_item->ID === (string) $menu_item->menu_item_parent ) {
$menu_item->menu_item_parent = 0;
}
$sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
if ( $menu_item->menu_item_parent ) {
$menu_items_with_children[ $menu_item->menu_item_parent ] = true;
}
}
// Add the menu-item-has-children class where applicable.
if ( $menu_items_with_children ) {
foreach ( $sorted_menu_items as &$menu_item ) {
if ( isset( $menu_items_with_children[ $menu_item->ID ] ) ) {
$menu_item->classes[] = 'menu-item-has-children';
}
}
}
unset( $menu_items, $menu_item );
/**
* Filters the sorted list of menu item objects before generating the menu's HTML.
*
* @since 3.1.0
*
* @param array $sorted_menu_items The menu items, sorted by each menu item's menu order.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$sorted_menu_items = apply_filters( 'wp_nav_menu_objects', $sorted_menu_items, $args );
$items .= walk_nav_menu_tree( $sorted_menu_items, $args->depth, $args );
unset( $sorted_menu_items );
// Attributes.
if ( ! empty( $args->menu_id ) ) {
$wrap_id = $args->menu_id;
} else {
$wrap_id = 'menu-' . $menu->slug;
while ( in_array( $wrap_id, $menu_id_slugs, true ) ) {
if ( preg_match( '#-(\d+)$#', $wrap_id, $matches ) ) {
$wrap_id = preg_replace( '#-(\d+)$#', '-' . ++$matches[1], $wrap_id );
} else {
$wrap_id = $wrap_id . '-1';
}
}
}
$menu_id_slugs[] = $wrap_id;
$wrap_class = $args->menu_class ? $args->menu_class : '';
/**
* Filters the HTML list content for navigation menus.
*
* @since 3.0.0
*
* @see wp_nav_menu()
*
* @param string $items The HTML list content for the menu items.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$items = apply_filters( 'wp_nav_menu_items', $items, $args );
/**
* Filters the HTML list content for a specific navigation menu.
*
* @since 3.0.0
*
* @see wp_nav_menu()
*
* @param string $items The HTML list content for the menu items.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$items = apply_filters( "wp_nav_menu_{$menu->slug}_items", $items, $args );
// Don't print any markup if there are no items at this point.
if ( empty( $items ) ) {
return false;
}
$nav_menu .= sprintf( $args->items_wrap, esc_attr( $wrap_id ), esc_attr( $wrap_class ), $items );
unset( $items );
if ( $show_container ) {
$nav_menu .= '</' . $args->container . '>';
}
/**
* Filters the HTML content for navigation menus.
*
* @since 3.0.0
*
* @see wp_nav_menu()
*
* @param string $nav_menu The HTML content for the navigation menu.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$nav_menu = apply_filters( 'wp_nav_menu', $nav_menu, $args );
if ( $args->echo ) {
echo $nav_menu;
} else {
return $nav_menu;
}
}
Hooks
- apply_filters( ‘pre_wp_nav_menu’,
string|null $output ,stdClass $args ) Filters whether to short-circuit the wp_nav_menu() output.
- apply_filters( ‘wp_nav_menu’,
string $nav_menu ,stdClass $args ) Filters the HTML content for navigation menus.
- apply_filters( ‘wp_nav_menu_args’,
array $args ) Filters the arguments used to display a navigation menu.
- apply_filters( ‘wp_nav_menu_container_allowedtags’,
string[] $tags ) Filters the list of HTML tags that are valid for use as menu containers.
- apply_filters( ‘wp_nav_menu_items’,
string $items ,stdClass $args ) Filters the HTML list content for navigation menus.
- apply_filters( ‘wp_nav_menu_objects’,
array $sorted_menu_items ,stdClass $args ) Filters the sorted list of menu item objects before generating the menu’s HTML.
- apply_filters( “wp_nav_menu_{$menu->slug}_items”,
string $items ,stdClass $args ) Filters the HTML list content for a specific navigation menu.
Here is the ready to use snippet (not a real contribution, more than anything else a shortcut):
Different menus for logged-in users
This example would cause a menu to show for logged-in users and a different menu for users not logged-in.
Removing the
ul
wrapThis example will remove the unordered list wrap around the list items. Replacing the wrap HTML with the
%3$s
specifier results in only outputting the HTML list content for the menu items, becauseitems_wrap
is built usingsprintf()
items_wrap
default before:[html]
<ul id="%1$s" class="%2$s">%3$s</ul>
[/html]
items_wrap
after:[html]
%3$s
[/html]
Example:
Using a Custom Walker Function
For deeper conditional classes, you’ll need to use a custom walker function (created in the
'walker' => new Your_Walker_Function
argument).The simplest way to build a new walker function is to copy and extend the default class (
Walker_Nav_Menu
) from /wp-includes/nav-menu-template.php and simply customize what you need.Example:
This custom walker function will add several conditional classes to your nav menu (i.e. sub-menu, even/odd, etc):
To clarify
link_before
vsbefore
, andlink_after
vsafter
The
link_before
/link_after
values are output WITHIN the link.Whereas,
before
/after
values are output OUTSIDE of the link, but within the list item.For example:
{before}{link_before}Link Text{link_after}{after}
Default example
Shows the first non-empty menu or
wp_page_menu()
.[html]
<?php wp_nav_menu() ; ?>
[/html]
Targeting a specific menu with no fallback to
wp_page_menu()
In the case that no menu matching menu is found, it seems that passing a bogus
theme_location
is the only way to prevent falling back to the first non-empty menu:By default, menus are encased with a
div
container. The options on this page show this parameter as a string to change the element used to contain the menu.However, it is not noted here that if you pass
false
as a value, the container is completely removed leaving just theul
menu element.How to show a placeholder menu if no menu is set up
Credit: Salcode.
If you’ve just installed WordPress and activated a theme, there may not be a primary menu yet set up in Appearance > Menus. For these times you may want to show your own, placeholder, menu until that menu is set up.
To do this, use the fallback callback function. Like so:
Simple shortcode for displaying a menu
this will allow you to display a menu in where ever you add the shortcode, lots of room to expand the $args but left it simple.
Usage Example:
[get_menu menu="Main Menu"]
Targeting a specific menu
How to add
.active
class to active menu itemHere is the code:
It is not mentioned that container parameter by default accepts only ‘div’, ‘nav’ and falsy value (”, false, null). If you want to extend accepted tag arguments and wrap menu parent ul tag in something else, you have to use wp_nav_menu_container_allowedtags filter and add desired tag to the array. However, for the most uses, default two tags are most logical.
How to add a parent class for menu item
Sometimes you may need to add a class to a menu item if it has sub-menus.
instead of large walkers you can simply filter menu item class names
Adding Conditional Classes to Menu Items
This example would let you add a custom class to a menu item based on the condition you specify. Don’t forget to change the condition.
Removing the default div container
In order to remove the default
div
container in which the menu is encased, just :Like in the following example :
The only accepted values for the
container
argument, by default, arediv
andnav
so any other value will cause it to not display.I’ve found really useful the
echo
arg.Menu slug
Menu slugs are constructed as:
[menu name]-menu
I.e., for menu named ‘main’, slug will be ‘
main-menu
‘You can find menu slugs in terms database table.
If you are looking for a way how to replace the menu item title (what’s displayed) with e.g. custom HTML or wrap the text in some HTML, you are looking for the `nav_menu_item_title` filter
The difference between the
'menu'
and'theme_location'
parameters in thewp_nav_menu()
function is not clearly explained in this article. However, I believe there is a distinction, as they appear to serve different purposes.The
'menu'
parameter relies on the name of the menu that is assigned when it is created in the WordPress admin panel. Therefore, it is recommended to match the parameter value to the name given when creating the menu. However, if this is not done, the ‘theme_location’ parameter will be used instead, which will display the menu that is attached or selected for that location. This location must be previously registered with theregister_nav_menu()
function in the functions.php file.Therefore, for developers, it is wise to set the
'theme_location'
parameter instead of the'menu'
parameter. Keep in mind that the way the menu is displayed is defined in the order described in the Usage section of this article.Make a shortcode to show menu in editor
To use simply:
[show_wp_menu menu="wp_menu_name_here" menu_class="my_menu_class"]
Adding a Word at the Beginning of the Menu
This example will allows you to add the word of your choice to the beginning of your menu as a list item. In this example, the word “Menu:” is added at the beginning. You may want to set an id on the list item (“item-id” in this example) so that you can use CSS to style it.
I’m a little confused about the parameters “before / after” and “link_before / link_after”.
This reference says “before / after” are inside the link, including the link-text, and “link_before / link_after” are outside the link, including the link.
What confuses me:
Some tutorials say, it’s exactly the other way round, (inside / outside):
here and here
I’m coming to the same result, when looking at one of my active WP-installations.
What is correct?