Trying to add a submenu underneath main menu to roots

Hey everyone,

I’m currently trying to add a submenu to a roots website instead of using the built-in bootstrap dropdowns.

That is, I would like it to look like this mock-up:

Is there a simple hook/filter-based approach to doing this, or am I going to have to bite the bullet and learn regex and rewrite a part of the roots menu walker?

You don’t really need regex. I would just change the class applied to dropdowns and restyle with CSS.

class Roots_Nav_Walker_Custom extends Roots_Nav_Walker {
  function start_lvl(&$output, $depth = 0, $args = array()) {
    $output .= "\n<ul class=\"dropdown-menu-custom\">\n";

Thanks for your help!

I seem to be having a little trouble getting these changes to be applied. I’ve tried inserting the snippet you gave me into both custom.php and nav.php with no luck. What am I missing here?

Add the class to lib/custom.php and then call the custom walker when you use wp_nav_menu. You need to pass the walker as an object.

wp_nav_menu(array('walker' => new Roots_Nav_Walker_Custom));

Hi there, thanks for this.

With the above method, the parent item is non-clickable since it expects to open the submenu with a toggle. How can I fix this?

Another thing I would like to achieve is to display the submenu as a standalone 2nd menu, outside of the main <ul> and active <li> and only on the active page. What is the easiest way to accomplish this?


Never mind the first question, that’s easy fixable by adding my own custom walker:

class Roots_Nav_Walker_Custom extends Walker_Nav_Menu {
	function start_lvl(&$output, $depth = 0, $args = array()) {
		$output .= "\n<ul class=\"sub-menu\">\n";
	function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
		$item_html = '';
		parent::start_el($item_html, $item, $depth, $args);
		if ($item->is_dropdown && ($depth === 0)) {
			$item_html = str_replace('<a class="dropdown-toggle" data-toggle="dropdown" data-target="#"', '<a', $item_html);
			$item_html = str_replace(' <b class="caret"></b></a>', '</a>', $item_html);
		} elseif (stristr($item_html, 'li class="divider')) {
			$item_html = preg_replace('/<a[^>]*>.*?<\/a>/iU', '', $item_html);
		} elseif (stristr( $item_html, 'li class="dropdown-header')) {
			$item_html = preg_replace('/<a[^>]*>(.*)<\/a>/iU', '$1', $item_html);
		$item_html = apply_filters('roots_wp_nav_menu_item', $item_html);
		$output .= $item_html;
	function display_element($element, &$children_elements, $max_depth, $depth = 0, $args, &$output) {
		$element->is_dropdown = ((!empty($children_elements[$element->ID]) && (($depth + 1) < $max_depth || ($max_depth === 0))));
		if ($element->is_dropdown) {
			$element->classes[] = 'has-submenu';
		parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);

But I’m not sure how I can output the <ul class="sub-menu"> outside the current element in the start_lvl function.

If you’re moving a submenu outside the current element then it’s not really a submenu anymore. Why not just create it with a second call to wp_nav_menu using a second level only walker?

You’re right, maybe a submenu is not the best term in this case.
I found a very useful method where you still can use the wp_nav_menu function with the original Roots walker, but with an extra sub_menu parameter:

1 Like

I’ve used Christian Varga’s solution in other non sage built sites, but I’m having issues with it working while using wp_boostrap_navwalker and soil plugin. Were you able to get his solution to work? I need to dig in a bit more but was hoping for some advice.

Yeah I got it working, but that also was in the non-sage time (roots version) where the wp_boostrap_navwalker was still default so haven’t tested it yet in the Sage versions.

yea, i can’t seem to get it working with sage. Maybe due to how soil cleans up the menu? I wondering if i’m over thinking it or if there is an easy solution for pulling either sub menu children from wp menu or even children of pages? Sill navigation my way around using sage