Creating a Mega Menu in WordPress (without a Plugin)

Last Updated May 5, 2017

I’ve been working on a build for a site where I designed a massive mega menu.

NationsU Mega Menu

There were a couple of tricks, here (the criteria):

  • I didn’t want the user to have to click to reveal the menu.
  • The top item doesn’t link to a page, it simply launches the mega menu.
  • Not all the items within the primary menu launch a mega menu. Home, FAQ, Blog, and Contact all go straight to those respective pages.
  • The home page uses an icon instead of text.
  • If I could everything in CSS, I wanted to go that route.
  • I wanted to do everything within WordPress’s default menu structure.
  • Search actually launches a search bar that takes over the navigation bar.

Nothing too terribly crazy, just challenging.

Setting up WordPress

I started by trying to figure out how to get all the content into WordPress so that all the pieces I need are available and could be pulled out.

Navigation structure

This part is the easiest part of the whole process. Go to Appearances > Menus.

WordPress, Appearance, Menus

Create a new menu, add items to it, drag and drop to arrange the order and proper nesting.

Lastly, you'll want to make sure the menu location is properly assigned.

WordPress Menu Locationtheme doesn't support menus or the location you need is not available, you can create that support within your functions.php file.

Open functions.php file.

You can search for register_nav_menus to keep all menu code together, otherwise, just navigate to the bottom of the file and add

register_nav_menus(array( // Using array to specify more menus if needed
        'primary-menu'   => __('Primary Menu', 'html5blank'), 	// Primary Navigation
    ));

In this particular case, we’re adding a menu location called “Primary Menu.”

You can add multiple locations by adding to the array:

register_nav_menus(array( // Using array to specify more menus if needed
		 // Primary Navigation
		 'primary-menu'   => __('Primary Menu', 'selfteachme'), 
	// Page Top Navigation	
		'page-top-menu' => __('Page Top Menu', 'seflteachme') 
	 ));

Once you’ve successfully added a location, it will be available on the Appearances > Menu page.

The Menu Descriptions

WordPress has a built in Menu Descriptions. Did you know that? Pretty cool. If you add a link to the menu, expand the box, and don’t see description as an option, click on Screen Options in the top, right corner. A panel will slide down and you can check description.

WordPress Menu Descriptionptions in the top, right corner.

While, we’re there, make sure CSS Classes is checked, too.

WordPress - CSS Class in Menus

Go back through each of the top level mega menu items and enter description content.

WordPress - Menu Description

The Icons

Let’s add classes to the home page and search level items. This will allow us to specifically target our display.

Open the home nav item and add the class nav-home.

Home Class

On the search item, add the class nav-search.

Nav Search Class

We’ll come back to the CSS we’ll actually write later. Remember, this step is all about making sure that the pieces are appropriately set up within WordPress.

Which items are Mega Menus?

While we're adding classes, let's add the class is-mega-menu to all the top level mega menu items.

Add has-mega-menu class

Coding It Out

One of the things that I like to do when coding is to write out the HTML structure first. Once the structure is there, I’ll make another pass with the CSS (or Sass) to adjust the look of it. Then, I’ll make a third pass, hotwiring the pieces into WordPress.

Three passes, that sounds like a lot of sets and a lot of work. I know. But, this allows me to focus on one thing at a time and one aspect of my code at a time: content (HTML), style / display (CSS), and functionality (PHP / WordPress). It also prevents me from getting ahead of myself.

The First Pass: The HTML

Let’s talk through our code first.

The navigation background extends the width of the page. Then, the navigation itself fits within the container or grid structure we’ve set up, meaning the maximum width is 1200px. This means that we’ll need a wrapper div that handles the background and a container div to contain the content.

Awesome, so far that gives us something like this:

<div class="primary-menu__wrapper">
		<div class="primary-menu">
			...
	</div><!-- /.primary-menu -->
</div><!-- /.primary-menu__wrapper -->

NOTE:

I wanted to point out the HTML comments, next to the closing div. I try not to over do it with those. There’s no reason to have a comment for every closing div, that would impede readability. But, I use them anytime I know I’ll have a large block of code, where I know it might be difficult to keep track of what <div> the </div> corresponds to.

The comment let’s me know it’s closing / a class: .primary-menu and /.primary-menu__wrapper.

NOTE:

I also wanted to draw you attention to the naming convention I’m using. Naming things in CSS is a topic all of its own. In fact, people specialize (BEM, SMACCS, and OOCSS) in these sort of things. It’s worth it’s own series of posts. But, for now, we’ll resort to the clif notes:

Child elements are denoted by two underscores: __. Modifiers use two dashes: --. Everything else is separated by a -, no camel casing.

If you’re quick, as I suspect you are, you may think, “Wait a second, you said __wrapper, it’s not a child element, it’s the parent.” True! Good catch. While some people would disagree with my structure, I’ll argue that the wrapper is not the focus or the main element of the module here, the container is. Therefore, it would only make sense that the __wrapper be named something secondary.

Moving on…

We’ll need an unordered list to contain our navigation elements. This might be another personal preference thing. But, I like to use the HTML5 <nav> element to specify that it’s navigation. I also like my navigation to be written as a <ul>, after all, it is a list of items.

Using that logic, this is the code inside our 2 divs:

<nav class="nav" role="nav">
	<ul>
		<li><a href="#">Home</a></li>
		<li><a href="#">Why NationsU?</a></li>
		<li><a href="#">Academics</a></li>
		<li><a href="#">Admissions</a></li>
		<li><a href="#">FAQ</a></li>
		<li><a href="#">Blog</a></li>
		<li><a href="#">Contact</a></li>
		<li><a href="#">Search</a></li>
	</ul>
</nav>

Let’s think back to the class names that we added to our WordPress menu. WordPress adds class names to the li element. Let’s go ahead and add them to our HTML:

<nav class="nav" role="nav">
	<ul>
		<li class="nav-home"><a href="#">Home</a></li>
		<li class="is-mega-menu"><a href="#">Why NationsU?</a></li>
		<li class="is-mega-menu"><a href="#">Academics</a></li>
		<li class="is-mega-menu"><a href="#">Admissions</a></li>
		<li><a href="#">FAQ</a></li>
		<li><a href="#">Blog</a></li>
		<li><a href="#">Contact</a></li>
		<li class="nav-search"><a href="#">Search</a></li>
	</ul>
</nav>

Perfect!

Now, let’s think about the mega menu. We’ve already decided that we want to use CSS to trigger the Mega Menu.

You can’t next the mega menu inside the <a> tag because the entire mega menu could only use one link. Nesting inside the <li> tag makes sense, though. It keeps everything the main nav item, with the sub navigation and doesn’t interfere with the link clicking.

For our mega menu, we’ll need:

  • A wrapper <div> for the entire mega menu
  • A <div> that contains the content within within the grid
  • A <div> to hold the content on the left
  • A <div> to hold the sub menu

Let’s see what this looks like:

<li class="is-mega-menu"><a href="#">Academics</a>
	<div class="mega-menu">
		<div class="mega-menu__content">
			...
		</div>
		<div class="mega-menu__subnav">
			...
		</div>
	</div>
</li>

Awesome! Things are looking good and coming together.

I know I glossed over the content, but especially considering everything else, it’s really easy. In the interest of being explicit, the content is simply a header, paragraph of content, and a link:

<h2>Title</h2>
<p>Lorem Ipsum</p>
<a href="#" class="learn-more">Learn More</a>

I told you it was easy!

For the subnav, we’ll use a similar approach as before:

<nav>
	<ul class="subnav">
		<li><a href="#">Item #1</a>
		<li><a href="#">Item #2</a>
		<li><a href="#">Item #3</a>
	</ul>
</nav>

We have all our HTML code! Altogether now:

<div class="primary-menu__wrapper">
		<div class="primary-menu">
			<nav class="nav" role="nav">
			<ul>
				<li><a href="#">Home</a></li>
				<li><a href="#">Why NationsU?</a></li>
				<li><a href="#">Academics</a>
					<div class="mega-menu">
				 		<div class="mega-menu__content">
								<h2>Title</h2>
							<p>Lorem Ipsum</p>
							<a href="#" class="learn-more">Learn More</a>
				 		</div>
						<div class="mega-menu__subnav">
								<nav>
								<ul class="subnav">
							 		<li><a href="#">Item #1</a>
										<li><a href="#">Item #2</a>
									<li><a href="#">Item #3</a>
									</ul>
							</nav>
				 		</div>
				 	</div>
				</li>
				<li><a href="#">Admissions</a></li>
				<li><a href="#">FAQ</a></li>
				<li><a href="#">Blog</a></li>
				<li><a href="#">Contact</a></li>
				<li><a href="#">Search</a></li>
			</ul>
		</nav>
	</div><!-- /.primary-menu -->
</div><!-- /.primary-menu__wrapper -->

Of course the content for each of the mega menu sections would be repeated, but in the interest of readability, I simply included the content for one Mega Menu item.

What do you think? It’s a lot. But hang in there. The HTML is the boring part. The CSS (or Sass) is where we make it look pretty. — and add the rollover functionality.

The Second Pass: The CSS (or Sass)

We’ll come back to the mega menu, but for now, let’s hide it so that it doesn’t create more problems than it’s worth:

.mega-menu {
	display: none;
}

Let’s start with the background color (white with 30% opacity) of the navigation bar, remember, we’re using a wrapper to hold everything and make sure our background goes to the edge of the browser.

.primary-menu {

		&__wrapper {
			background: rgba(white, 0.3);
			width: 100%;
		}
}

NOTE:

Curious about the &__ syntax? First things first, this is Sass.

If you’ll remember, the amperstand & references the parent item. — Here, the parent is .primary-menu.

Did you notice the double underscores __ are connected to the &? No space. That tells us that when the CSS gets compiled, it will attach the parent class name to whatever follows: .primary-menu__wrapper.

This is a neat trick, especially if you’re in to things like the CSS BEM naming conventions.

Now, let’s limit the width of the primary-menu and add some basic text-styles:

$nav-bar-height : 62px;

.primary-menu {
	@include container(1200px);
	font-family: sans-serif;
	font-weight: bold;
	height: $nav-bar-height;
}

In the code above, you’ll notice I made the $nav-bar-height a variable, then referenced that within the .primary-menu style.

There are several other places in my code where I want to reference the navigation height. If I need to change the height of the bar, better that I do it in one place (the variable), instead of finding every instance. That’s the beauty of variables!

Let’s style the navigation links. I’m going to be more specific in with my styles to prevent conflicts within the mega menu.

I’ll explain. We already know our HTML structure. There are 2 <nav> systems: one for the main bar and another for the sub navigation.

Two Navigation Structures

In CSS, specificity makes a difference, a big difference. For the primary navigation, if I’m more specific and say, “It’s the <nav> that’s a direct child of .primary-menu,” it won’t conflict with the sub navigation.

.primary-menu {
		...

	> .nav > ul {
		display: flex;
		flex-direction: row;
		justify-content: space-between;
		list-style-type: none;
		margin: 0;
		padding: 0;
	}
}

NOTE:

The > tells CSS that those elements are direct children.

We removed any default margin and padding, took away the bullets, and applied flexbox.

Have you ever used flexbox before? It’s pretty awesome and more than worth your time to figure it out.

Here, we’re trying to make sure that the navigation spans the entire width of the page, regardless of how wide (or narrow) it is. Obviously, it will breakdown when we get to smaller screens (we can handle that with breakpoints), but this display is harder and more complicated to create otherwise.

Let’s look at the actual li styling:

.primary-menu {
		...

	> .nav > ul {
		...
 
		> li {
			border-top: 2px solid transparent;

			&:hover {
				background: white;
				border-top: 2px solid navy;
			}
		}
	}
}

We kept it pretty simple. We’ll offset more of the work to the a styles. All we did was add a border-top to the list item. On hover, we want a border to appear above the nav item and the link’s background to be white. If we just left it at that, by only styling the :hover the nav element would shift down on rollover. The transparent border prevents the shift. It creates a border, you just can’t see it until hover.

Now, for the heavy lifting on the a tag:

.primary-menu {
		...

	> .nav > ul {
		...
 
		> li {
			...
 
			> a {
				box-sizing: border-box;
				color: navy;
				display: block;
				letter-spacing: 1px;
				line-height: 40px;
				padding: 10px 35px;
				text-decoration: none;
				text-transform: uppercase;
			}
		}
	}
}

We adjusted the color of the navigation and some of the basic text styling. We removed the underline for the link and adjusted the padding. Why both with the padding, if we already have the spacing between items? This will effect, how width the navy border on the li will be. But, we didn’t want it to bust and mess our measurements, so we used the box-sizing: borer-box; property. Lastly, we made the link a block (by default it’s inline).

Got it! The basic styling for the menu bar is done!

Styling for the Primary Menu Bar

Next, let’s adjust the home and search icons.

If you’ll remember, we added class names to the home (.nav-home) and search links (.nav-search). Now, we can hook onto them:

// home link
.nav-home a {
	 background: url('../img/icon-home.svg') center center no-repeat;
	 text-indent: -9999px;

	 &:hover {
		background: white url('../img/icon-home.svg') center center no-repeat; 
	 }
}

// search link
.nav-search a {
	 background: url('../img/icon-search.svg') center center no-repeat;
	 text-indent: -9999px;

	 &:hover {
		background: white url('../img/icon-search.svg') center center no-repeat; 
	 }
}

The text-indent: -9999px; property, removes the text with CSS. — well, if you want to get technical, it doesn’t really remove the text, it shifts it to the left 9999 pixels, so the user never sees it.

NOTE:

You may have noticed I’m using svgs. SVGs are pretty awesome. You can think of them like Illustrator (vector) files for the internet. With raster graphics (.jpgs, .pngs, .gifs), you can always go smaller, but you can’t go bigger. — you’re dealing with pixels and there’s only so much information available.

With vector files, everything is created with numbers, coordinates, and math. In fact, if you open an SVG file in your text editor, you’ll see it’s all text. The benefit is you can go as big or small as you need, you can the colors of the graphic without creating an extra file (my favorite feature), and the file sizes are incredibly small. — I told you they were awesome!

Here, I’m simply reference the SVG within the background tag, just as you’d do with a .jpg, .gif, or .png.

If you saw the &:hover, that’s Sass working it’s magic. The & references the parent (.nav-home and .nav-search) and applies styles to the :hover state. The compiled CSS is just .nav-home:hover and .nav-search:hover.

I created another background property on the :hover state. It might look like redundancy, but I wanted the background to be white, without removing the SVG.

Home Hover

That’s done!

Now, let’s style the mega menu itself. When we first started styling the navigation, we applied this style to keep the mega menu out of our way:

.mega-menu {
	display: none;
}

Let’s remove that, so we can see what we’re working with.

Similar to how we handled the navigation bar, let’s add our framework and start working through the elements, one by one.

.mega-menu {
	position: relative;
 
	 &__wrapper {
		background: white;
		box-shadow: 0 4px 4px -2px #777;
		left: 0;
		min-height: 320px;
		padding: 35px 0 45px;
		position: absolute;
		width: 100%;
		z-index: 9000;
	 }
}

The wrapper forces the mega menu to display full browser width, with a white background. You’ll notice, I’ll also added a box-shadow, this provides a slight drop shadow at the bottom of the menu, to add some differentiation from the content underneath.

We also set the z-index to 9000 so that it will appear above all the content on the site. — We don’t want it to appear at the very top layer, because we want to save room for other items, such as modal windows to appear above the mega menu.

I gave the mega menu a minimum-height, this ensures that the mega menu will at least be 320px tall. This value seemed like a good average based on what I had designed.

Lastly, the wrapper is responsible for the positioning. We gave our mega menu a position: absolute; then placed it flush with the left side of the browser: left: 0.

Positioning is relative to the parent. mega-menu__wrapper is the parent of mega-menu. I wanted to make sure I gave mega-menu a position: relative;.

Now let’s style the content on the left side of our mega menu:

&__content {	
	box-sizing: border-box;
	border-right: 10px solid orange;
	float: left;
	margin-left: 8.47%;
	margin-right: 1.69%
	padding-left: 33px;
	padding-right: 120px;
	width: 57.6%

		/* mega menu title */
		h2 {
			font-family: sans-serif;
			font-size: em(28px);
			color: $dark-slate-blue;
			letter-spacing: 1px;
			margin-bottom: 10px;
			margin-top: 25px;
		}

		/* description */
		p {
			font-family: serif;
			font-size: em(18px);
			font-weight: normal;
			line-height: em(28px, 18px);
			margin-bottom: 30px;
		}
 
		 /* learn more button */
		.learn-more {
			border: 2px solid navy;
			border-radius: 3px;
			color: navy;
			font-family: sans-serif;
			font-size: 22.5px;
			font-weight: bold;
			line-height: 1px;
			margin-bottom: 25px;
			padding: 12px 30px;
			text-decoration: uppercase;

			&:hover {
				background: navy;
                color: white;
            }
		}
}

Most of these styles are pretty straightforward. They’re just simple text formatting. Just the same, I’ll point out a few things:

If you noticed, the first style is &__content. If you’ll remember, the & references the parent. This entire block of Sass is nested within the .mega-menu tag. So, that the rendered CSS class is .mega-menu__content.

Wondering where I got some of my weird width, margin-left, and margin-right percentages? I’m actually using the Susy framework. I didn’t want Susy’s @include span(7); to confuse you, so I simply plugged in the rendered values instead.

The em() function that you’re see for the font-size and line-height properties is a Sass function that I like to use. It allows me to plug in pixel sizes and renders ems (which is better for sizing typography) within my compiled CSS.

NOTE:

I’ve written a post that talks specifically about the Sass functions, extends, and mixins that I use.

For the subnavigation styling, same sort of deal:

/* sub navigation within the mega menu */
&__subnav {
	float: left;
	margin-right: 0;
	width: 32.2%;

	.subnav {
		list-style-type: none;
		margin: 25px 0 25px 40px;
		padding: 0;

		li {
			font-size: em(16px);
			line-height: em(20px, 16px);
			margin-bottom: 20px;

			a {
				color: navy;
				text-decoration: none;
				text-transform: uppercase;

				&:hover {
					color: orange;
				}
			}
		}
	}
}

Most of the styles are pretty straightforward, at least compared to the styling work we did at the beginning. We’re simply styling an unordered list for the subnavigation.

By wrapping, the content and the sub navigation in their own separate divs, it gives us more control over how these items wrap and interact next to each other.


Woo hoo!! You should give yourself a pat on the back. We got all the display work done! Now, we just need to connect everything to WordPress.


The Third Pass: The PHP (hotwiring WordPress)

Originally I assumed I would have to use WordPress’s custom navigation walker.

NOTE:

I loathe WordPress’s Walker class. — Basically, it’s a class that can be used to walk through the navigation tree, hierarchy, whatever you want to call it.

If this diagram gives you any indication of what you’re getting yourself into…

WordPress Walker Class
Image from TutsPlus.com.

Feel free to check out the documentation, but it can be pretty convoluted and frustrating.

Spoiler alert, I figured a work around that was much easier to work with. But, if you’re determined to beat the Walker class, here are a couple of great resources I would point you to:

When I was doing my research, I found this article: Create Fully Custom WordPress Mega Menu, No Plugins Attached. I didn’t use their code verbatim, but it was incredibly helpful. We’ll use similar principles to hot wire our navigation.

First things first. The thing about working in layers, like we’ve done with each code pass is that it allows us to focus on one aspect of our code at a time. Since we have our framework and know how we want our HTML to be displayed, it makes plugging in our PHP much simpler.

Looking at our code as a whole, we want to consider two parts:

  • The dynamic parts. These are the parts that can change. It’s content inside of WordPress that we can pull out and plug into our HTML. Remember? Way back at the beginning of this post, we plugged a bunch of content into WordPress. Now is the time to pull it all out.
  • The repeating parts. These are things like the naivgation and sub navigation items that repeat each other. We’ll use on line (like <li>...</li>) and iterate over it with a foreach loop.

With that in mind, let’s tackle the primary navigation items first.

Here’s our HTML:

<nav class="nav" role="nav">
	<ul>
		<li><a href="#">Home</a></li>
		<li><a href="#">Why NationsU?</a></li>
		<li><a href="#">Academics</a></li>
		<li><a href="#">Admissions</a></li>
		<li><a href="#">FAQ</a></li>
		<li><a href="#">Blog</a></li>
		<li><a href="#">Contact</a></li>
		<li><a href="#">Search</a></li>
	</ul>
</nav>

Within our <ul> block, let’s grab the primary-menu loaded into WordPress. First, we’ll get an array of all the navigation locations:

<?php $locations = get_nav_menu_locations();

NOTE:

If you’re new to PHP, just think of an array as a filing cabinet, or, a list of items.

get_nav_menu_locations() is a function that WordPress provides. If you’re so inclined, you can read the documentation, but it returns that array we want.

Now, let’s check to see if our primary-menu actually exists within the list of locations WordPress returned. Why bother, especially when we know we entered this content earlier? If for some reason, something happens to our primary menu, we don’t our theme to break and throw errors. It’s just responsible programming.

// if there's a location for the primary menu
if ( isset( $locations['primary-menu']) ) {

Now, that we know it exists, let’s grab the terms.

$menu = get_term( $locations['primary-menu'], 'nav_menu');

get_term() is another function WordPress provides. You can look at the documentation, but, essentially, we’re passing in the information we retrieved from our $locations array and telling WordPress, we want the nav_menu taxonomy.

WordPress returns another array. Last check before we get to the real good stuff.

We checked to make sure the location exists, now let’s check to make sure there are are actually items within our primary menu:

// if there are items in the primary menu
if ( $items = wp_get_nav_menu_items( $menu->name ) ) {

wp_get_nav_menu_items is another one of those WordPress functions. — and this array is the meat we’re looking for.

This code is pretty smart. Not only does it check to see if there are items in the primary menu, but it also assigns it to a variable called $items.

We can cycle through each of the items in the array and get all of our menu items.

NOTE:

If you’re curious about all the information that’s included in the array, one of my favorite tricks is to use the PHP function var_dump(VARIABLE);. For us, that would look like: var_dump($menu); and the browser will display all the content that’s stored within that variable.

I’ll forewarn, you though, the output is n-a-s-t-y. It displays the content directly on your page, in the browser. It’ definitely not something you would want to leave in your production code. But, it’s quick and dirty for development.

When I was working on this project, I was surprised by just how much information is stored within the $menu array. Trying to wade through all of it can be frustrating. So, I found another trick, that allowed me to display the contents of the $menu array without destroying the display on my page.

I’m a huge fan of Chrome Dev Tools, especially the Console panel. Ideally, I was hoping I could display the contents of $menu within the console. — and I did. I wrote a post specifically about it.

// loop through all menu items to display their content
foreach ( $items as $item ) {
	...
}

A PHP foreach loop allows you to easily plug in the array of items we want to cycle through $items and reference once of those items as $item.

Here’s a much simpler array (or list of content), that might make this easier to understand:

<?php $children = ['Isaac', 'Adele', 'Jake']; ?>

If I wanted to list each of my children’s names programmatically:

<?php foreach ( $children as $one_child) {
	echo $one_child . '<br />'; 
} ?>

This would display:

Isaac
Adele
Emma

Our WordPress menu array is much more complicated, but as you can see the foreach loop makes it easy to look at each item in the array individually.

Back to it.

I mentioned earlier that the array contains everything, including secondary navigation.

So, when we’re looking at each navigation item, let’s check to make sure that it’s a primary nav item and not a child. If it’s a child, we can just skip over it.

// if the current item is not a top level item, skip it
	if ($item->menu_item_parent != 0) {
	continue;
}

menu_item_parent is provided in on our array. If the item has a parent, it will give us the ID of the parent, otherwise, it will be 0 (to designate, no parent).

Next, let’s get the ID for our navigation:

// get the ID of the current nav item
$curNavItemID = $item->ID;

We’ll use this information later.

Now is the fun part (if you haven’t been having already). We want to get the class list. This is information we plugged into WordPress earlier.

// get the custom classes for the item
// (determined within the WordPress Appearance > Menu section)
$classList = implode(" ", $item->classes);
echo "<li class=\"{$classList}\">";
echo "<a href=\"{$item->url}\">{$item->title}</a>";

$item->classes contains an array of all the classes. In order to use it in our HTML, we need it to be flat text, with each class name separated by a space. implode() is a PHP function that will flatten our array for us and separate each item in the array with whatever we tell it (a space). Neat, how this function does exactly what we’re looking for!

NOTE:

$item->url and $item->title are available to us in that meaty array.

Now, let’s fill in our mega menu content:

// build the mega-menu
// if 'mega-menu'  exists within the class
if ( in_array('has-mega-menu', $item->classes)) { ?>

When we were working in WordPress, we added the class has-mega-menu to primary navigation items that trigger the mega menu. We can look for those!

If the class has-mega-menu exists within our class list, we can build a mega menu.

<div class="mega-menu__wrapper js-mega-menu">
	<div class="mega-menu">
			<div class="mega-menu__content">
				<h2><?= $item->post_title; ?></h2>
            <p><?= $item->description; ?></p>
            <a href="<?= $item->url; ?>" class="learn-more">Learn More</a>
        </div>

        <div class="mega-menu__subnav">
	        <nav>
					<ul class="subnav">
                <?php // cycle through the menu items and get the subnav
	            foreach ( $items as $subnav) {
		            if ( $subnav->menu_item_parent == $curNavItemID) {
			            echo "<li><a href=\"{$subnav->url}\">{$subnav->title}</a>";
						} // close if
					} // close foreach ?>
					</ul>
				</nav>
			</div>

		</div>
</div>

I know this is a bigger chunk of code than what we’ve been dealing with, but it’s straightforward. Don’t let it intimidate you! It’s our HTML that we wrote earlier, except we’re plugging in values (like we’ve been doing) from our array.

The only thing that might need a little more explanation is the subnavigation loop. We actually take our $items array from earlier. Yes, the one that has everything in it. Before, we filtered out items that weren’t primary nav items, now, we want to do the opposite. Except, we only want subnav items that are related to the parent nav item we’re working on.

Remember when I said earlier, let’s save the primary navigation ID, we’ll use it later? Now’s the time!

We can use the ID of the current parent nav item we’re working on, to see if it matches the sub nav’s menu_item_parent ID. If it does, display that item.


Whew!

And we’re done. I bet you wondered if this moment would ever come. — and congrats to you, for sticking with it for the entire length of the post.


The Final Code

I’ve placed the final snippets of code in GitHub gists. Feel free to copy / paste / fork / use, however you need.


The Conversation

  • This is awesome Amy, thank you! Especially love the sidestepping around the dang walker class 🙂

    • Amy Dutton

      Great! — and yeah, I absolutely hate the walker class 🙂

  • jaycbrf4

    The only problem I see here is that you can not have HTML inside the mega menu. Adding some description text can be done in a much simpler method. What would be nice to see is an actual mega menu with photos, links and responsiveness built in.

    • Amy Dutton

      Actually, you could use the same principles outlined in this article, but include widgets or ACF to get customized HTML and image support.