How to add a separator between menu items in a wp_nav_menu() the easy way

WordPress wp_nav_menu() function has lots of useful arguments for customizing your menu, but it's missing an essential one: a menu item separator. For example, if you want to add a | (vertical slash) or a · (middot), or any other separator of your choice, the ideal solution would be to add it as a function argument like 'item_sep' => '·'

[panel type=”info”]This tutorial is based on the assumption that you already registered your menu in functions.php. Read about registering menus on WordPress Codex[/panel]

WordPress wp_nav_menu() function has lots of useful arguments for customizing your menu. These are the arguments you can currently use:

 '',
        'menu_class' => '',
        'menu_id' => '',
        'container' => '',
        'container_class' => '',
        'container_id' => '',
        'fallback_cb' => '',
        'before' => '',
        'after' => '',
        'link_before' => '',
        'link_after' => '',
        'echo' => '',
        'depth' => ,
        'walker' => '',
        'theme_location' => '',
        'items_wrap' => '',
        'item_spacing' => '',
    )
); ?>

As you can see, it’s missing an essential argument: a menu item separator. For example, if you want to add a | (vertical slash) or a · (middot), or any other separator of your choice, the ideal solution would be to add it as a function argument like

'item_sep' => '·'

Such separators are useful especially for secondary navigations, like footer menus, but unfortunately WordPress doesn’t share this opinion. Although an elegant solution like a function argument is only wishful thinking at present, it doesn’t mean that adding dividers is an impossible task. Let’s explore our options here (hint: skip to options 2 and 3 for the easiest and recommended solutions):

Creating a walker

(read more about walkers on WordPress Codex).
This is a good choice for devs and advanced users, if you want more complex menu customizations in addition to adding a separator. An example of a menu walker would be this:

class Walker_Nav_Menu_With_Separator extends Walker_Nav_Menu {

        /**
	 * Ends the element output, if needed.
	 *
	 * @see Walker_Nav_Menu::end_el()
	 *
	 * @param string   $output Passed by reference. Used to append additional content.
	 * @param WP_Post  $item   Page data object. Not used.
	 * @param int      $depth  Depth of page. Not Used.
	 * @param stdClass $args   An object of wp_nav_menu() arguments.
	 */
	public function end_el( &$output, $item, $depth = 0, $args = array() ) {
		if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
			$t = '';
			$n = '';
		} else {
			$t = "\t";
			$n = "\n";
		}

		$separator = isset($args->item_separator) ? $args->item_separator : '';

		$output .= "{$separator}{$n}";
	}

}

And you will also have to add it to your menu:

wp_nav_menu(array(
            'theme_location' => 'secondary',
            'depth' => 1,
            'container_class' => 'footer-nav',
            'container' => 'nav',
            'item_separator' => '·',
            'walker' => new Walker_Nav_Menu_With_Separator
        )
);

It’s not a bulletproof solution, because the walker grants you access to the menu item per se, not the parent too. This means that there is no way to target the first or the last menu item, so the dividers will be added for all the items (including the last one and the submenus). CSS styles will have to be used in order to make the dividers “invisible” where necessary. In short, this is an unnecessarily complicated and tedious option.

Using filters

Just add this in functions.php:

add_filter('wp_nav_menu_items', 'wcr_wp_nav_menu_items', 11, 2);

function wcr_wp_nav_menu_items($items, $args) {
	$separator = '·';
	
	// process the list
	$document = new DOMDocument();
	$document->loadHTML(mb_convert_encoding($items, 'HTML-ENTITIES', 'UTF-8'));

	$lis = $document->getElementsByTagName('li');

	if (empty($lis)) {
		return $items;
	}

	// rebuild the list
	$new_items = array();
	foreach ($lis as $li) {
		$new_items[] = $document->saveXML($li);
	}

	return implode($separator, $new_items);
}

Please note that this will add separators for all the menus in all locations. If you have multiple menu locations and only wish to add dividers for one menu or a subset of them, you need to add this:

add_filter('wp_nav_menu_items', 'wcr_wp_nav_menu_items', 11, 2);

function wcr_wp_nav_menu_items($items, $args) {
        // you can define multiple theme locations
	$theme_locations = array('secondary', 'another_location_example');
	$separator = '·';
	
	// add the separator just for a defined list of theme_locations
	if(!isset($args->theme_location) || !in_array($args->theme_location, $theme_locations)){
		return $items;
	}
	
	// process the list
	$document = new DOMDocument();
	$document->loadHTML(mb_convert_encoding($items, 'HTML-ENTITIES', 'UTF-8'));

	$lis = $document->getElementsByTagName('li');

	if (empty($lis)) {
		return $items;
	}

	// rebuild the list
	$new_items = array();
	foreach ($lis as $li) {
		$new_items[] = $document->saveXML($li);
	}

	return implode($separator, $new_items);
}

CSS only

Where WordPress falls short, CSS comes to the rescue with the pseudo-classes :before and :after
For example if your menu container has the class .footer-nav, add this in style.css:

.footer-nav ul	{
	list-style-type: none;
	margin: 0;
	padding: 0;
}

.footer-nav ul li	{
	display: inline-block;
	padding: 0 6px 0 0;
}

.footer-nav ul li:before {
	content: "\00b7";
	display: inline-block;
	margin-right: 8px;
}

.footer-nav ul li:first-child:before,
.footer-nav .children li:before {
	content: "";
	display: none;
}

This will add a · (middot) as a separator. If you prefer a | (vertical slash), replace \00b7 with \007c. If you want any other character, you can find a complete list of glyphs here.

You can delete .footer-nav .children li:before if your menu has 'depth' => 1, (meaning that it doesn’t display sub-menus/children).

One comment

  1. Love this! But where (on the WP side) do I set the locations to get this to work?
    $theme_locations = array(‘secondary’, ‘another_location_example’);

    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *