How to style a WordPress navigation menu bar using CSS

Writing styles for navigation menus nowadays is mostly a routine job; after all, CSS has made strides in the last few years, we can even mimic hover-intent on menus without any JavaScript at all (a topic which we’ll cover on a dedicated tutorial).

WordPress isn’t much different, the native menu functionality is powerful and versatile enough to give us with exactly the HTML we’d require, plus a few more options for providing a better user experience to our users. The aim of this post is to take you through those and provide a complete solution on how to style a native WordPress navigation menu.

Adding a WordPress menu to our website

Displaying a nav menu on your WordPress site is generally very simple, it only takes a couple of lines:

<?php
  // functions.php
  register_nav_menus( array(
    'main' => __( 'Main Menu', 'mytheme' ),
  ) );
?>

<?php // somewhere in header.php ?>
<header class="header">
  <nav class="nav">
    <?php
      if ( has_nav_menu( 'main' ) ) {
        wp_nav_menu( array(
          'theme_location' => 'main-menu',
          'container'      => '',
          'menu_class'     => 'navigation-main',
        ) );
      }
    ?>
  </nav>
</header>
Adding a WordPress nav menu

Then all you have to do is go through the usual process of creating a menu and adding items to it via the admin dashboard.

For this tutorial we’ll assume our menu’s structure is as follows:

├── Home
├── Blog
|   ├── News
|   ├── Sports
|   ├── Fashion
|   |   ├── Article #1 Title
|   |   ├── Article #1 Title
|   ├── Business
├── About
└── Contact

So basically a menu of three levels (our final styles will support infinite nested levels). This would produce roughly the following markup:

<header class="header">
  <nav class="nav">
    <ul class="navigation-main">
      <li class="current-menu-item">
        <a href="#">Home</a>
      </li>
      <li class="menu-item-has-children">
        <a href="#">Blog</a>
        <ul>
          <li><a href="#">News</a></li>
          <li><a href="#">Sports</a></li>
          <li class="menu-item-has-children">
            <a href="#">Fashion</a>
            <ul>
              <li><a href="#">Article #1</a></li>
              <li><a href="#">Article #2</a></li>
            </ul>
          </li>
          <li><a href="#">Business</a></li>
        </ul>
      </li>
      <li><a href="#">About</a></li>
      <li><a href="#">Contact</a></li>
    </ul>
  </nav>
</header>
Final HTML markup

I said “roughly” before, and that’s because although the actual HTML structure will remain the same throughout our website, WordPress will add very specific classes depending on what page we’re currently viewing.

We need to keep in mind that WordPress adds the following three important classes to the menu’s <li> elements with the following rules:

  • .menu-item-has-children: Menu items that contain sub-items (just like “Blog” and “Fashion” in our example).
  • .current-menu-item: Menu items that correspond to the currently viewed page (so in our example we just opened the homepage as “Home” contains this class).
  • .current-menu-ancestor: Menu items that are ancestors (parents, grandparents) of the menu item which corresponds to the currently rendered page.

We’ll very soon see how to use these classes in our stylesheets.

WordPress also adds a few other classes that generally do not concern us in a normal menu, but can become useful in very specific use-cases so be sure to check them out.

Giving the menu some structure

With the markup in place it’s time to start styling our menu. We’ll be using Sass instead of vanilla CSS. Sass comes very handy here, especially in styling menus where there’s a lot of nesting involved. It’s extra useful because (as with most things in WordPress) we need to style everything on our menu under its main class namespace (.navigation-main). This will prevent our styles from “bleeding” out to other potential menus we might have in our theme.

In general, when I write styles for a navigational component with a complex functionality I like to split styles into blocks “structure/functionality” and “presentation/appearance” styles, even if that means that I have to be a bit more verbose or even duplicate some selectors. I’ve found that it provides a clear separation of concerns and becomes a much more maintainable paradigm, especially when the component’s presentational requirements become very complex (think animations, different colors for each sub-item, etc). We’re not going to deal with that kind of stuff right now, but it’s a good practice to have in mind when the CSS concerns are so cleanly separated in a particular isolated component such as this one.

.header {
  display: flex;
  align-items: center;
}

.nav {
  width: 100%;
}

.navigation-main {
  //
  // Structural Styles
  //
  margin: 0;
  padding: 0;
  list-style: none;
  width: 100%;
  text-align: center;

  ul {
    margin: 0;
    padding: 0;
    list-style: none;
    position: absolute;
    left: -999em; // "Hide" the sub-menus
  }
  
  li {
    display: inline-block;
    position: relative;
    text-align: left; // Reset text alignment

	// First sub-menus appear below the menu item
    &:hover {
      > ul {
        left: auto;
      }
    }
    
	// All other levels appear on the right
    li {
      display: block;
      
      &:hover {
        > ul {
          left: 100%;
          top: 0;
        }
      }
    }
  }
  
  //
  // Presentational Styles (we'll add them soon)
  //
}
The menu's main structural styles

This is all it takes to imbue the functionality of a standard navigation menu, and support infinite nested levels. We absolutely position all sub-menus, offset them out of screen (we don’t set their display property to “none”) and we reposition them on hover making them visible.

Here’s how this all looks:

0

Try hovering over “Blog”, and then “Fashion”.

It’s kinda ugly though, so let’s make it a bit prettier.

Adding appearance and visual clues to the menu

Pretty basic stuff, some background and link colors:

$background-color: #145474 !default;
$link-color: #fff !default;
$link-active-color: #ffcc03 !default;

.header {
  background-color: $background-color;
  display: flex;
  align-items: center;
}

.nav {
  width: 100%;
}

.navigation-main {
  //
  // Structural Styles
  //

  // Same as what we wrote before
  
  //
  // Presentational Styles
  //

  // Global menu link styles
  a {
    color: $link-color;
    text-decoration: none;
    display: block;
    white-space: nowrap;
    padding: 10px 15px;
  }
  
  // Active & hover styles
  a:hover {
    color: $link-active-color;
  }
  
  // Submenu styles
  ul {
    background-color: $background-color;
  }
}

And here’s how it looks now:

0

Although our menu is starting to look good it’s missing a couple of things which are quite important for a good user experience.

First of all, it would be nice if while the user hovers over different nav items their parents and ancestors remain highlighted. It helps with visual identification of the current hovered path and it just looks better, so let’s fix that with the following extra selector:

...

// Active & hover styles
a:hover,
li:hover > a { // This trick keeps all active nodes in the navigation path colored active
  color: $link-active-color;
}

...
Highlighting the currently hovered path

Secondly, we must highlight the nav items which correspond to the currently viewed page. That means that, if a user is at “Home” we should highlight the “Home” item, and if a user is viewing “Article #1” the menu should highlight all its ancestors up to and including “Blog”.

Here’s where the classes we mentioned before come into play, and it’s very simple to handle the above scenarios:

...
  
// Active & hover styles
a:hover,
li:hover > a,
.current-menu-item > a,
.current-menu-ancestor > a {
  color: $link-active-color;
}
  
...
Highlighting current menu items and their ancestors

Check out how “Home” now remains active and how if you navigate all the way through “Article #1” its ancestors will also stay highlighted:

0

Adding visual clues for items with children

It’s also a standard good practice to provide a visual clue (such as an arrow) for items with nested sub-menus. The tricky part here is that items belonging to the first level (main menu) should have arrows pointing down, while the rest should point right. As a final step let’s see how we’d go about this:

...

  // First level item arrows
  .menu-item-has-children {
    > a {
      &::before {
        content: "";
        position: absolute;
        right: 0;
        top: 50%;
        transform: translateY(-50%);
        border: 4px solid transparent;
        border-top-color: currentColor; // Nifty trick to always keep the arrow colored the same as the menu item's text in every different state 
      }
    }
  }
  
  // Second level item arrows
  ul {
    .menu-item-has-children {
      > a {
        &::before {
          right: 2px;
          border-top-color: transparent;
          border-left-color: currentColor;
        }
      }
    }
  }
  
...
Adding dropdown arrows

Note that I’m creating CSS triangles with the border trick but you’re free to use whatever you wish, e.g. FontAwesome icons (fa-angle-down is a good choice).

And the result:

0

Pretty good! Looks like we’re all done with our menu, perfectly styled and ready for deployment.

Going the extra mile: Making the menu accessible

Our menu is pretty much finished but it would be awesome if we could make it accessible to keyboard users, navigating with the tab button. For this we’ll need to write some very basic JavaScript and only add a couple of classes to our CSS:

jQuery(function ($) {
  var siteNavigation = $('.navigation-main');
  
  siteNavigation.find( 'a' ).on( 'focus blur', function() {
    $( this ).parents( 'li' ).toggleClass( 'focus' );
  });
});
Making the menu accessible is easy!

This toggles a class on a menu item every time we focus on its link. This way we can simply alter our CSS to display the sub-menus when that class is present:

...

  li {
    display: inline-block;
    position: relative;
    text-align: left; // Reset text alignment

    &.focus, // Display the menus both on hover and when .focus is present!
    &:hover {
      > ul {
        left: auto;
      }
    }
    
    li {
      display: block;
      
      &.focus,
      &:hover {
        > ul {
          left: 100%;
          top: 0;
        }
      }
    }
  }

...

  a:hover,
  li:hover > a,
  .focus > a, // And color them as well
  .current-menu-item > a,
  .current-menu-ancestor > a {
    color: $link-active-color;
  }

...

And we’re done! Let’s look at a complete example with more nested levels and the current menu item on a lower level than the main menu. Try hovering over and tabbing through and it should all work great!

0

Let us know in the comments how you prefer to style your WordPress menus or any tips and tricks you might have up your sleeve, and, oh! if you’d also like to convert your menu into a mobile drawer for mobile devices we’ve got you covered :)

Subscribe to our newsletter.

Get fresh WordPress content straight into your inbox. We hate spam more than you do.

Leave a Reply

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