Today, all sites have a navigation menu at the top of each page, and WordPress websites are no exception. In this article, we want to implement and learn the development of the custom navigation menu in WordPress using WordPress’ own functions and code.
Each WordPress theme has a number of places to display menus on the page, which are called menu locations. Every standard WordPress theme should have at least one menu location. For example, the main menu of the website is a menu location, but be careful that the template designer only determines the location of the menu and not the menu items. The definition of menu items is the responsibility of the user and the website designer.
Using the menus in the WordPress dashboard, you can define your menu items. You can even set the depth of the menus. Some menus support the mega-menu and WordPress sidebar menus, which we will talk more about in the following.
Now we will examine and implement the types of menus and how to use them.
Definition of menu location
As mentioned, menu locations are the locations of menus that are defined by the designer and developer of the template, and their location on the page is also determined by the same developer. Now, if you want to design a template for yourself or customize an existing template and want to add a menu to a part of the site, first of all, you must define the menu location in WordPress.
For this, open the functions.php file of your template and put the following codes in it.
if (!function_exists('hs_sample_setup_header')) :
function hs_sample_setup_header()
{
register_nav_menus(
array(
'main-menu' => esc_html__('Main Menu', 'textdomain'),
)
);
}
add_action('after_setup_theme', 'hs_sample_setup_header');
endif;
In this example, we are going to add the main menu to the header of our website. Here we have defined a menu location with the name Main Menu and ID main-menu.
If you go to the menu section in the WordPress dashboard, you will see the menu location.
If you define your menu items in this section and check Main Menu and then save, the items will be displayed in the Main Menu section, but because the location of the menu is not specified in the template, you will not see it yet. So let’s go to determine the location of the menu on the page.
Insert menu location in WordPress template
As we said, we want the desired menu to be displayed in the header of the website, so open the header.php file of your template and put the following code in it.
<?php if (has_nav_menu('main-menu')) : ?>
<nav class="navbar">
<div id="bscollapse" class="navbar-box">
<?php
wp_nav_menu(array(
'theme_location' => 'main-menu',
'container' => 'div',
'menu_class' => 'nav navbar-nav',
));
?>
</div>
</nav>
<?php endif; ?>
Paste this code where you want it to appear. For example, we put it after the Logo of the website. If you don’t know how to develop a WordPress header, we have a good tutorial for you.
In WordPress, the wp_nav_menu function is used to display the menu. This function has many features that you can use to personalize your menu as much as possible.
In this code, first of all, using the ID we defined in the menu location, we check whether the menu has an item or not. According to the structure of HTML5, navigation menus must be placed inside the nav tag, but since WordPress does not add these tags, we have to add them ourselves.
As we said, using the wp_nav_menu function, we display the menu with the main-menu ID. For this, it is enough to put the menu ID in the theme_location function.
If you visit your website you will see the menu items displayed.
Here we are done with the PHP codes and the template to display our menu, but it still doesn’t have the right style. Now let’s go to add the menu style.
WordPress menu styling
In WordPress, the wp_nav_menu function converts menu items to HTML codes and displays them. This function adds some attributes and classes to the menu that we use to style the menu.
Now open the style.css file of your template and add the following codes to it.
nav ul {
list-style: none;
display: flex;
flex-wrap: wrap;
padding: 0;
margin: 0;
}
nav ul li:not(.sub-menu li) {
width: auto;
position: relative;
}
nav ul a {
text-decoration: none;
color: #3C4048;
padding: 5px 10px 5px 0;
font-size: 18px;
font-weight: 700;
display: flex;
align-items: center;
}
nav ul a:hover {
color: #00ABB3;
}
nav .sub-menu {
position: absolute;
left: 0;
min-width: 250px;
background: #fff;
box-shadow: #3C4048 2px 2px 10px -4px;
visibility: hidden;
opacity: 0;
margin-left: 1px;
}
nav .sub-menu li {
width: 100%;
position: relative;
}
nav .sub-menu a {
background: #fff;
color: #3C4048;
font-size: 14px;
font-weight: 400;
padding: 10px 5px;
}
nav .sub-menu a:hover {
background: #00ABB3;
color: #fff;
}
nav a>span.menu-text {
padding: 15px 10px;
width: 100%;
}
nav .sub-menu .menu-item-has-children a>span.dashicons {
padding-right: 10px;
}
nav .menu-item-has-children:hover>.sub-menu {
visibility: visible;
opacity: 1;
}
nav .sub-menu .sub-menu {
left: 100%;
top: 0;
}
If you refresh your website page, you will see that the menu is displayed with the appropriate style.
Just keep in mind that this is a menu with a simple style and you can add various styles to your WordPress menu with CSS codes. For example, we have even used Bootstrap to create and style the template and menu.
Separate the desktop menu from the mobile menu in WordPress
WordPress has provided a function called wp_is_mobile that you can use to detect whether the user’s device is desktop or mobile. In this example, we use this function to separate the desktop menu from the mobile menu.
There is no need to do this for simple menus because a menu can be styled for both desktop and mobile using CSS media query codes. But if the menu is a mega menu or has many items, it will be difficult to properly style that menu on mobile.
Therefore, in this example, we separate the menu of the desktop version and the menu of the mobile version in order to design a simple menu for mobile that increases the user experience.
To do this, open your functions.php file and edit the above code as follows.
if (!function_exists('hs_sample_setup_header')) :
function hs_sample_setup_header()
{
register_nav_menus(
array(
'main-menu' => esc_html__('Main Menu', 'textdomain'),
'mobile-menu' => esc_html__('Mobile Menu', 'textdomain'),
)
); }
add_action('after_setup_theme', 'hs_sample_setup_header');
endif;
In this section, we have defined the menu location for WordPress for mobile. The point that you should keep in mind here is that the menu location of the mobile phone and the desktop are not different from each other and only their names and identifiers are different.
In other words, we have not yet informed WordPress that this menu should be displayed for mobile. To do this, open the header.php file again and edit the codes of the menu section as shown below.
<?php if(wp_is_mobile()): ?>
<?php if (has_nav_menu('mobile-menu')) : ?>
<nav class="navbar">
<button class="navbar-toggler" type="button" onclick="hss_open_menu(this)">
<span class="dashicons dashicons-menu-alt3"></span>
</button>
<div id="bscollapse" class="navbar-box">
<button class="menu-close" onclick="hss_close_menu(this)">
<span class="dashicons dashicons-no-alt"></span>
</button>
<?php
wp_nav_menu(array(
'theme_location' => 'mobile-menu',
'container' => 'div',
'menu_class' => 'nav navbar-nav',
));
?>
</div>
</nav>
<?php endif; ?>
<?php else: ?>
<?php if (has_nav_menu('main-menu')) : ?>
<nav class="navbar">
<div id="bscollapse" class="navbar-box">
<?php
wp_nav_menu(array(
'theme_location' => 'main-menu',
'container' => 'div',
'menu_class' => 'nav navbar-nav',
));
?>
</div>
</nav>
<?php endif; ?>
<?php endif; ?>
As you can see, we used the wp_is_mobile function to separate the mobile and desktop versions of the menu.
Because the mobile version has a toggle button that displays the menu when clicked, we have to add it because the WordPress wp_nav_menu function considers all menus as one and does not distinguish between mobile and desktop menus.
The problem with this function is that the function recognizes tablets as mobile phones, while the display of the website on a tablet is closer to a desktop than to a mobile phone. Unfortunately, there is currently no function to recognize the tablet in WordPress, so we have to use the traditional method using CSS codes to style the tablet.
More customization of menus
Through the wp_nav_menu function, we can customize our menu. For example, if we want to add an element or content to the start and end of the menu item, we can do the following code.
wp_nav_menu([
'theme_location' => 'main-menu',
'container' => 'div',
'menu_class' => 'nav navbar-nav',
'link_before' =>'<span>Before ',
'link_after' => ' After</span>',
]);
In this code, at the beginning of each item, we have put the span tag and the phrase “Before”, and at the end of each item, we have put the phrase “After” and the end tag span. If you go to your menu on the page, you will see the menu that has been added to the first and last of each item.
This type of customization is actually used in cases where we want to apply changes to menu expressions, but if we want to change the structure of menus in WordPress, we must use the walker
class.
wp_nav_menu([
'theme_location' => 'main-menu',
'container' => 'div',
'menu_class' => 'nav navbar-nav',
'walker' => new HS_Menu_Walker(),
]);
The walker class can be used to change the menu structure. For example, WordPress does not support the Bootstrap menu, so if we want to use the Bootstrap menu in WordPress, we must adjust the structure of the menus according to Bootstrap, for which we use the walker class. If you want to use Bootstrap in your template, we have provided a tutorial for you.
Walker class for WordPress menu development
As we said above, the class walker is used to change and development of the WordPress menu structure. In order to use this class, we must create a class that is extended from this class.
This class includes functions, the most important of which are the following four functions.
- end_el: closes the output of the element if needed.
- end_lvl: Adds favorites to the end of the menu item.
- start_el: used to display items before displaying the item.
- start_lvl: Favorite items can be added to the first menu list by this function.
For example, the following class is an example of the walker class, which is the default example of WordPress itself.
class Walker_Nav_Menu extends Walker {
public $tree_type = array( 'post_type', 'taxonomy', 'custom' );
public $db_fields = array(
'parent' => 'menu_item_parent',
'id' => 'db_id',
);
public function start_lvl( &$output, $depth = 0, $args = null ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "\t";
$n = "\n";
}
$indent = str_repeat( $t, $depth );
$classes = array( 'sub-menu' );
$class_names = implode( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
$output .= "{$n}{$indent}<ul$class_names>{$n}";
}
public function end_lvl( &$output, $depth = 0, $args = null ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "\t";
$n = "\n";
}
$indent = str_repeat( $t, $depth );
$output .= "$indent</ul>{$n}";
}
public function start_el( &$output, $data_object, $depth = 0, $args = null, $current_object_id = 0 ) {
$menu_item = $data_object;
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "\t";
$n = "\n";
}
$indent = ( $depth ) ? str_repeat( $t, $depth ) : '';
$classes = empty( $menu_item->classes ) ? array() : (array) $menu_item->classes;
$classes[] = 'menu-item-' . $menu_item->ID;
$args = apply_filters( 'nav_menu_item_args', $args, $menu_item, $depth );
$class_names = implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $menu_item, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
$id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $menu_item->ID, $menu_item, $args, $depth );
$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
$output .= $indent . '<li' . $id . $class_names . '>';
$atts = array();
$atts['title'] = ! empty( $menu_item->attr_title ) ? $menu_item->attr_title : '';
$atts['target'] = ! empty( $menu_item->target ) ? $menu_item->target : '';
if ( '_blank' === $menu_item->target && empty( $menu_item->xfn ) ) {
$atts['rel'] = 'noopener';
} else {
$atts['rel'] = $menu_item->xfn;
}
$atts['href'] = ! empty( $menu_item->url ) ? $menu_item->url : '';
$atts['aria-current'] = $menu_item->current ? 'page' : '';
$atts = apply_filters( 'nav_menu_link_attributes', $atts, $menu_item, $args, $depth );
$attributes = '';
foreach ( $atts as $attr => $value ) {
if ( is_scalar( $value ) && '' !== $value && false !== $value ) {
$value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
$attributes .= ' ' . $attr . '="' . $value . '"';
}
}
$title = apply_filters( 'nav_menu_item_title', $title, $menu_item, $args, $depth );
$item_output = $args->before;
$item_output .= '<a' . $attributes . '>';
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $menu_item, $depth, $args );
}
public function end_el( &$output, $data_object, $depth = 0, $args = null ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "\t";
$n = "\n";
}
$output .= "</li>{$n}";
}
}
Using this class, you can change the items you need and customize the menu according to your needs.
To use this class, just include it in your WordPress template and use it in the wp_nav_menu function.
wp_nav_menu(array(
'theme_location' => 'main-menu',
'container' => 'div',
'menu_class' => 'nav navbar-nav',
'walker' => new Walker_Nav_Menu()
));
In this section, we only define an object of the desired class to the walker menu item inside the function. If you don’t introduce the walker to the menu, WordPress will use its default class listed above.
WordPress menu development using hooks
WordPress hooks like add_filter can also be used to customize and develop the menu code. Above, we discussed the walker class and explained how to work with it. If you want to change one of the class functions and use the default WordPress class, you can use the add_filter hook.
For example:
add_filter( 'walker_nav_menu_start_el', 'wpdocs_menu_item_custom_output', 10, 4 );
function wpdocs_menu_item_custom_output( $item_output, $item, $depth, $args ) {
$menu_item_classes = $item->classes;
if ( empty( $menu_item_classes ) || !in_array( 'menu-item-target', $menu_item_classes ) ) {
return $item_output;
}
ob_start();
?>
<ul class="custom-sub-menu">
<li class="custom-menu-item">
<a href="#custom-url">
<?php _e( 'My text', 'wpdocs' ); ?>
</a>
</li>
</ul>
<?php
$custom_sub_menu_html = ob_get_clean();
$item_output .= $custom_sub_menu_html;
return $item_output;
}
In this example, we use the default WordPress class to display menu items, but by customizing one of the functions (start_el), we can customize the output of the function as desired.
You can use the list of hooks below to personalize the menus as much as possible.
- nav_menu_css_class
- nav_menu_item_args
- nav_menu_item_id
- nav_menu_item_title
- nav_menu_link_attributes
- walker_nav_menu_start_el