How to use the US Web Design Standards with WordPress menus

Tutorial

I’m building a WordPress site that incorporates the draft US Web Design Standards (USWDS). It was going swimmingly until I began to style the vertical sidebar navigation.

Problem: WordPress menus have their own classes that differ from the ones used by the USWDS. In order to properly apply the USWDS styles, we need to reconcile these class names.

Example:

WordPress menu, as rendered in HTML:

<aside id="secondary" class="sidebar widget-area" role="complementary">
	<div class="menu-bureau-directory-container">
		<ul id="menu-bureau-directory" class="menu">
			<li id="menu-item-286" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-286">
				<a href="">Parent link</a>
			</li>
			<li id="menu-item-129" class="menu-item menu-item-type-post_type menu-item-object-page current-menu-ancestor current-menu-parent current_page_parent current_page_ancestor menu-item-has-children menu-item-129">
				<a href="">Current page</a>
				<ul class="sub-menu">
					<li id="menu-item-130" class="menu-item menu-item-type-post_type menu-item-object-page current-menu-item page_item page-item-124 current_page_item menu-item-130">
						<a href="">Child link</a>
					</li>

Recommended code from USWDS:

<div class="usa-grid-full">
	<aside class="usa-width-one-fourth">
		<ul class="usa-sidenav-list">
			<li>
				<a href="">Parent link</a>
			</li>
			<li>
				<a class="usa-current" href="">Current page</a>
				<ul class="usa-sidenav-sub_list">
					<li>
						<a class="usa-current" href="">Child link</a>
					</li>

Option 1: In the .css stylesheet, rename the relevant USWDS classes with the WordPress classes.

For example:
.usa-sidenav-list –> .menu
.usa-sidenav-sub_list –> .sub-menu

This would be rather simple to do, but I wanted to keep the USWDS stylesheet intact, mainly so that when they update it in the future, I can simply replace my stylesheet with their new one without too much worry that it will break my sidebar navigation styles. Basically, I’m betting the the USWDS stylesheet will change before WordPress changes the wp_nav_menu function. Thus:

Option 2 (what I chose): Modify the output of wp_nav_menu so that the rendered code uses the USWDS classes.

Step 1: Modify some of the default parameters in wp_nav_menu. So I called the menu in the sidebar.php file by using this code:

wp_nav_menu(array(
	'menu'=>'bureau-directory',
	'menu_class' => 'usa-sidenav-list',
	'container' => 'div', 
	'container_class' => 'usa-grid-full',
));

(Note: “bureau-directory” is the name of my custom menu in WordPress.)

That gives us the following changes in the rendered HTML:

<aside id="secondary" class="sidebar widget-area" role="complementary">
	<div class="usa-grid-full">
		<ul id="menu-bureau-directory" class="usa-sidenav-list">
			<li id="menu-item-286" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-286">
				<a href="">Parent link</a>
			</li>
			<li id="menu-item-129" class="menu-item menu-item-type-post_type menu-item-object-page current-menu-ancestor current-menu-parent current_page_parent current_page_ancestor menu-item-has-children menu-item-129">
				<a href="">Current link</a>
				<ul class="sub-menu">
					<li id="menu-item-130" class="menu-item menu-item-type-post_type menu-item-object-page current-menu-item page_item page-item-124 current_page_item menu-item-130">
						<a href="">Child link</a>
					</li>

Closer, but it still doesn’t give us the ability to rename the sub-menu classes, so our sub-menu items aren’t styled properly. So…

Step 2: We have to create a custom walker class.

improperly styled menu

The first-level items are styled correctly, but not the second level items.

Now, this was the first time I’d created a walker class, so I looked for an example online. Turns out the Broadcasting Board of Governors is also using WordPress for their sites, and they too are using the USWDS. And they very kindly shared their code on GitHub. Yay! So I used the code they had in their functions.php file and I called it from the wp_nav_menu function in sidebar.php. (It looks to me like the BBG developers may have based it on the code previously published here.)

Code in sidebar.php:

wp_nav_menu(array(
	'menu'=>'bureau-directory',
	'menu_class' => 'usa-sidenav-list',
	'container' => 'div', 
	'container_class' => 'usa-grid-full',
	'walker' => new themeslug_walker_nav_menu()
));

Code I added to functions.php (brace yourself: it’s a little long):

/**
 * Extend Walker function to add usa-sidenav-sub_list class to submenu navigation items
 */
class themeslug_walker_nav_menu extends Walker_Nav_Menu {
    // add classes to ul sub-menus
    function start_lvl( &$output, $depth = 0, $args = array() ) {
        // depth dependent classes
        $indent = ( $depth > 0  ? str_repeat( "\t", $depth ) : '' ); // code indent
        $display_depth = ( $depth + 1 ); // because it counts the first submenu as 0
        $classes = array(
            'sub-menu',
            'usa-sidenav-sub_list',
            //'usa-current',
            //( $display_depth % 2  ? 'menu-odd' : 'menu-even' ),
            ( $display_depth >=2 ? 'sub-sub-menu' : '' ),
            'menu-depth-' . $display_depth
        );
        $class_names = implode( ' ', $classes );
        // build html
        $output .= "\n" . $indent . '<ul class="' . $class_names . '">' . "\n";
    }
    //
    // add main/sub classes to li's and links
    function start_el(  &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        global $wp_query;
        $indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
        // depth dependent classes
        $depth_classes = array(
            ( $depth == 0 ? 'main-menu-item' : 'sub-menu-item' ),
            ( $depth >=2 ? 'sub-sub-menu-item' : '' ),
            //( $depth % 2 ? 'menu-item-odd' : 'menu-item-even' ),
            'menu-item-depth-' . $depth
        );
        $depth_class_names = esc_attr( implode( ' ', $depth_classes ) );
        // passed classes
        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $class_names = esc_attr( implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) ) );
        $current_class_names = '';
        // if the class array includes "current-menu-item" or "current_page_item" then set variable to "usa-current"
        if( in_array('current-menu-item', $classes) || in_array('current_page_item', $classes)){
             $current_class_names = 'usa-current';
        }
        // build html
        $output .= $indent . '<li id="nav-menu-item-'. $item->ID . '" class="' . $depth_class_names . ' ' . $current_class_names . ' ' . $class_names . '">';
        // link attributes
        $attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
        $attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
        $attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
        $attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';
        $attributes .= ' class="' . $current_class_names . '"';
        $item_output = sprintf( '%1$s%3$s%4$s%5$s%6$s',
            $args->before,
            $attributes,
            $args->link_before,
            apply_filters( 'the_title', $item->title, $item->ID ),
            $args->link_after,
            $args->after
        );
        // build html
        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }
}
sidebar navigation almost correctly styled

Here you can see the sub-menu elements are styled correctly, but there is no blue left border next to the current parent item (in this case, “P – Political Affairs”)

We’re getting warmer! But as you can see in the screenshot, it’s still missing something. The USWDS code for sidebar navigation displays a blue left border next to the current (parent) item, and makes the wording bold and blue:

what the USWDS sidebar navigation looks like

The elusive blue bar!

This is the USWDS .css that produces said blue bar, blue text, etc:


.usa-sidenav-list a.usa-current {
  border-left: 4px solid #0071bc;
  color: #0071bc;
  font-weight: 700;
  padding-left: 1.4rem;
}

Hmm. Let’s look at the rendered code of that parent item. The US Web Design Standards call for a class of “usa-current” in the <a> tag:

<a class="usa-current" href="">Current page</a>

But in our code, the class is unnamed:

<li id="nav-menu-item-129" class="main-menu-item  menu-item-depth-0  menu-item menu-item-type-post_type menu-item-object-page current-menu-ancestor current-menu-parent current_page_parent current_page_ancestor menu-item-has-children">
	<a href="http://prototype.mollymoran.com/p-political-affairs/" class="">P – Political Affairs</a>

It looks like the walker class isn’t passing the “usa-current” class to the parent element. WordPress gives us several choices for referencing this element; I chose to use “current_page_parent”:

<li id="nav-menu-item-129" class="main-menu-item  menu-item-depth-0  menu-item menu-item-type-post_type menu-item-object-page current-menu-ancestor current-menu-parent current_page_parent current_page_ancestor menu-item-has-children">
	<a href="http://prototype.mollymoran.com/p-political-affairs/" class="">P – Political Affairs</a>

Step 3: This is a simple fix. Returning to the walker class code in functions.php, I simply added code so that it would also pass “usa-current” to the <a> tag following a “current_page_parent” element (additions in yellow):

// passed classes
        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $class_names = esc_attr( implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) ) );
        $current_class_names = '';
        // if the class array includes "current-menu-item" or "current_page_item" or "current_page_parent" then set variable to "usa-current"
        if( in_array('current-menu-item', $classes) || in_array('current_page_item', $classes) || in_array('current_page_parent', $classes)){
             $current_class_names = 'usa-current';
        }
        // build html

Et voila! The code is now rendered thusly:

<a href="http://prototype.mollymoran.com/p-political-affairs/" class="usa-current">P – Political Affairs</a>

And it looks right too:

the sidebar menu styled properly

Ahhhh…styled properly, both parent and child.

For bonus points – making the menu collapsible

At this point I noticed one other difference between my WordPress menu and the sleek USWDS menu: theirs expanded/collapsed and mine didn’t. I noticed that thanks to the walker code I borrowed from BBG (thanks, BBG!), each sub-level menu was now ordered and labeled with classes such as .menu-depth-1, .menu-depth-2, etc. This made it easy for me to add the expanded/collapsed functionality with simple .css additions that reference the menu-depth classes:

.menu-depth-1 {
display: none;
}

.usa-current .menu-depth-1 {
display: block
}

.usa-current .menu-depth-1 .menu-depth-2 {
display: none;
}

.usa-current .menu-depth-1 .usa-current .menu-depth-2 {
display: block;
}

(This will expand/collapse a 3-level menu. If you have more levels than that, repeat the pattern above for .menu-depth-3 and beyond. But only after taking a good hard look at whether you really need that many levels of nesting, because it’s probably not very user-friendly.)

And that’s it. A properly-styled and coded sidebar menu in WordPress that is collapsible.

a sidebar menu that is styled properly and collapses and expands

Perfectly proper in every way!

Leave a Reply

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