Tania Rascia Web Design and Development

Skip Navigation
Responsive Dropdown Navigation Bar

Responsive Dropdown Navigation Bar

By Tania Rascia  /  96 responses

A tutorial on how to make a responsive navigation bar using Sass. The navigation will contain a dropdown field, and collapse into a hamburger menu on mobile.

Bootstrap and Foundation have fantastic navbars that you can use if you choose to base your layout on their framework. For my own projects, I chose to make a customizable responsive dropdown navbar with an animated hamburger menu. The navigation is built on Sass, adaptable, and requires very little jQuery. It was inspired by Flaunt.js by Todd Motto.

There’s a lot that goes into building a navbar like this, so I’ll go over the specifics. Frameworks are great, but I think it’s a great idea for every developer to create their own navigation at some point to understand how it works.

The Demo

See the Pen Responsive Dropdown Navigation Bar by Tania (@taniarascia) on CodePen.

I would suggest opening a new pen on Codepen and doing this tutorial step by step to see how it works.

The HTML

Let’s start with the menu itself. It’s a regular list, wrapped in a semantic nav tag.

<nav>
  <div class="nav-mobile">
    <a id="nav-toggle" href="#!"><span></span></a>
  </div>
  <ul class="nav-list">
    <li><a href="#!">Home</a></li>
    <li><a href="#!">About</a></li>
    <li><a href="#!">Services</a>
      <ul class="nav-dropdown">
        <li><a href="#!">Web Design</a></li>
        <li><a href="#!">Web Development</a></li>
        <li><a href="#!">Graphic Design</a></li>
      </ul>
    </li>
    <li><a href="#!">Pricing</a></li>
    <li><a href="#!">Contact</a></li>
  </ul>
</nav>

A list with no styling applied. Everything in the nav-mobile class will not appear until we begin working on the small device view. Setting the links to #! will ensure that no action takes place on click.

The SCSS

I like to use Sass, or more specifically, SCSS. There’s a lot of nesting going on in these navbars, and we can prevent repetition in the code with Sass. Additionally, variables will drastically improve the ease of color and size customization.

First, I’m going to set a few variables.

$content-width: 1000px;
$breakpoint: 799px;
$nav-height: 70px;
$nav-background: #262626;
$nav-font-color: #ffffff;
$link-hover-color: #2581DC;

$content-width will be the max width of the content within the navigation bar. $breakpoint determines at which width the media query breakpoint will take effect. Obviously named variables are created for size and colors.

The Sass Skeleton
nav {
  ul {
    li {
      a {
        &:hover {
        }
        &:not(:only-child):after {
        } 
      } // Dropdown list
      ul li {
        a {
        }
      }
    }
  }
}

Now we begin filling it in. We’ll float the entire nav to the right, remove the bullet points on the list and any pre-determined browser padding.

nav {
  float: right;
  ul {
    list-style: none;
    margin: 0;
    padding: 0;
  }
}

Now we float the list items to the left and style the a tag. The li will be set to position: relative, which doesn’t do anything yet, but will be explained a few steps down.

li {
  float: left;
  position: relative;
  a {
    display: block;
    padding: 0 20px;
    line-height: $nav-height;
    background: $nav-background;
    color: $nav-font-color;
    text-decoration: none;
  }
}

I’ve set the nested a tag to display:block with some padding, and gave it our previously determined colors. This is a dark navbar, but you can just as easily revert the colors for a light navbar.

a {  
  &:hover {
    background: $link-hover-color;
    color: $nav-font-color;
  }
  &:not(:only-child):after {
  padding-left: 4px;
  content: ' ▾';
}

The hover is simple – I’m just changing the background color of the entire a tag. Next, we have some pretty interesting CSS3.

&:not(:only-child):after

The full path of this code is nav ul li a:not(:only-child):after. This means that the code will apply to any a tag in our nav list that is NOT an only child, aka any dropdown. The :after means it comes after the output of the tag. I’ve decided that to specify any nav item as a dropdown, it will be followed by a unicode arrow – ▾ (#9662).

} // Dropdown list
ul li {
  min-width: 190px;
  a {
    padding: 15px;
    line-height: 20px;
  }

A small bit of styling is applied to the nested uls. I’ve given the li a minimum width, so that the dropdown width won’t vary based on content. I’ve changed the padding and line-height of the dropdown a, because the styling cascades down from the parent.

Positioning

Absolute and relative positioning remove items from the normal flow of the document. Learn CSS Layout has a very good, simple explanation of how positioning works. The important part to remember for this dropdown nav is that an position:absolute element will be placed relative to a position: relative element. You can think of the absolute element being nested within the relative element.

We already set the li to position: relative earlier. Now we’re going to add a new, absolutely positioned class. z-index: 1 guarantees that the dropdown will display on top of any content. And I added a box shadow, as is standard for dropdowns.

.nav-dropdown {
  position: absolute;
  z-index: 1;
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
}

Add display: none; so that we can toggle it later with JavaScript.

The jQuery

We’ll begin adding jQuery.

(function($) { // Begin jQuery

})(jQuery);

And tell the function to run on DOM ready.

(function($) {
  $(function() { // DOM Ready

  // Insert all scripts here

  });
})(jQuery);

Activating the dropdown is extremely simple. I devised this specific method, and haven’t seen it used on any other dropdown nav, and seems to work quite well. I’m going to target any a in the menu that has children, and toggle the .nav-dropdown class.

$('nav ul li > a:not(:only-child)').click(function(e) {
  $(this).siblings('.nav-dropdown').toggle();
});
  1. When the CSS path nav ul li > a:not(:only-child) is clicked on…
  2. Toggle (change the display property of) that specific nav-dropdown class.

$(this) specifies that it only targets only what was clicked on, and not every instance of that CSS path.

But what’s that (e) for? If you happen to have two dropdowns in the nav, and click on both of them, they both open. We want to prevent that behavior, and force only one dropdown to be open at a time. Inside of that same function, add:

$('.nav-dropdown').not($(this).siblings()).hide();
      e.stopPropagation();

This hides all of the dropdowns, and stopPropagation(); prevents that action from taking place. We attach it to e and place that e in the function.

There’s one more thing: I want the dropdown to hide if I click away from it at any point. We’ll hide it by setting a click function to the entire html tag.

$('html').click(function() {
      $('.nav-dropdown').hide();
    });

Here’s the entire jQuery so far.

(function($) {
  $(function() {
    $('nav ul li > a:not(:only-child)').click(function(e) {
      $(this).siblings('.nav-dropdown').toggle();
      $('.nav-dropdown').not($(this).siblings()).hide();
      e.stopPropagation();
    });
    $('html').click(function() {
      $('.nav-dropdown').hide();
    });
  });
})(jQuery);

Mobile

Now we have a fully functional dropdown nav. The next step is to turn it into a “hamburger” menu on mobile collapse. We’re going to create a square in the top right of the screen where the hamburger will live.

.nav-mobile {
  //display: none;
  position: absolute;
  top: 0;
  right: 0;
  background: $nav-background;
  height: $nav-height;
  width: $nav-height;
}

display:none is commented out so we can work on it right now. Later I will refer back to this.

Create a media query based on the mobile breakpoint.

@media only screen and (max-width: $breakpoint) {
// Insert all mobile styles here

}

For now, hide the ul and we’ll work on the hamburger.

nav {
  ul {
    display: none;
  }
}

Elijah Manor created a great CSS animated hamburger icon, and we’re going to use that method. You can read his tutorial to learn more about how this works. For my part, I condensed it for Sass.

The concept behind how it works is that a span class in the #nav-toggle id has a :before and an :after. The span is displayed as a thin, wide block level element that looks like a line. The before and after raise and lower the line, creating three lines.

Finally, the jQuery comes in and adds an .active class to the span, which rotates the :before and :after, creating an X.

#nav-toggle {
  position: absolute;
  left: 18px;
  top: 22px;
  cursor: pointer;
  padding: 10px 35px 16px 0px;
  span,
  span:before,
  span:after {
    cursor: pointer;
    border-radius: 1px;
    height: 5px;
    width: 35px;
    background: $nav-font-color;
    position: absolute;
    display: block;
    content: '';
    transition: all 300ms ease-in-out;
  }
  span:before {
    top: -10px;
  }
  span:after {
    bottom: -10px;
  }
  &.active span {
    background-color: transparent;
    &:before,
    &:after {
      top: 0;
    }
    &:before {
      transform: rotate(45deg);
    }
    &:after {
      transform: rotate(-45deg);
    }
  }
}

Toggle the .active span.

$('#nav-toggle').on('click', function() {
  this.classList.toggle('active');
});

Now you have a hamburger icon that animates on click action, but doesn’t do anything yet.


The final addition to our jQuery code will toggle the nav ul on click.

$('#nav-toggle').click(function() {
  $('nav ul').toggle();
});

Perfect! The hamburger toggles the menu. Our jQuery functionality is complete.

Fixing the disappearing navbar bug

I’ve seen many navbars that have a bug that causes the menu to disappear on desktop if you already toggled the view on mobile. This is an issue that is mostly noticed by developers, as your average user probably isn’t constantly resizing screens and toggling menus. However, this is a very simple problem to fix. If you’ll notice in the original HTML, I’ve set the ul class to nav-list, but haven’t referenced it yet. With this simple code, I’m going to ensure that the menu is always displayed on large screen sizes.

@media screen and (min-width: $breakpoint) {
  .nav-list {
    display: block !important;
  }
}

Now is the time to uncomment display: none; from the .nav-mobile class. We want it to be invisible until mobile collapse.

Now go back to your @media only screen and (max-width: $breakpoint) query. Place this at the top.

.nav-mobile {
  display: block;
}

We have to apply some styles to the mobile menu. First, set the nav to take up 100% of the viewport. Remove the left float from the list. We’ll set some padding and height to the a tag, and extra left padding to the nested uls.

nav {
  width: 100%;
  padding: $nav-height 0 15px;
  ul {
    display: none;
    li {
      float: none;
      a {
        padding: 15px;
        line-height: 20px;
      }
      ul li a {
        padding-left: 30px;
      }
    }
  }
}

Set .nav-dropdown to static, otherwise it will overflow onto the other list items.

.nav-dropdown {
  position: static;
  }

There you have it – a completely responsive navigation menu. It does everything that it needs to do, but it’s just floating there. A lot of tutorials just stop at this point, and I used to be confused as to how to incorporate that menu into the navbar. Fortunately, this is the easiest part.

The Navigation Bar

Go back to your HTML from the beginning of the tutorial. Wrap the entire nav in this code.

<section class="navigation">
  <div class="nav-container">
    <div class="brand">
      <a href="#!">Logo</a>
    </div>
    <!-- <nav></nav> -->
  </div>
</section>

There are three layers to this code:

.navigation – the outer wrapper for the navbar. Specifies the height and color, and will stretch the full width of the viewport.

.navigation {
  height: $nav-height;
  background: $nav-background;
}

.nav-container – the inner wrapper for the navbar. Defines how far the actual content should stretch.

.nav-container {
  max-width: $content-width;
  margin: 0 auto;
}

.brand – within .navigation and .nav-container, there are two colums – .brand on the left side, and nav on the right. The first thing I did with the nav list was float it to the right. Most of this is just styling; the important part is the absolute positioning and left float.

.brand {
  position: absolute;
  padding-left: 20px;
  float: left;
  line-height: $nav-height;
  text-transform: uppercase;
  font-size: 1.4em;
  a,
  a:visited {
    color: $nav-font-color;
    text-decoration: none;
  }
}


And that’s the end of this tutorial! If you didn’t want to read through any of that, here’s the complete, fully functional three aspects – HTML, SCSS and JS. If you don’t use Sass and just want the CSS, you can very easily convert it to CSS.

If you’re copying the code in CodePen, make sure to include the jQuery library or it won’t work!

View Source View Demo

Email List

Get friendly updates, infrequently.

Tania Rascia

Hiya. I'm Tania, a web developer from Chicago. Hope you enjoyed my ad-free, bullshit-free site. If you liked it, tell someone about it!

 GitHub  Twitter

Write a response

Your email address will not be published.

All code will be displayed literally.

Discussion

  • Serge Goujon says:

    Very cool and thorough tutorilal. Wish it was jQuery-free as well! )))

  • nagu says:

    How to give link to parent menu? Because if i give link to parent menu in mobile dropdown is not opening only because its directly redirecting to that particular page.

  • Norman Euker says:

    Very nice tutorial!

    Do you know how to make it so the Services item will be an active link, and not just trigger display of the sublist?

  • skube says:

    "I’m going to target any a in the menu that has children, and toggle the .nav-dropdown class."

    I think this is inaccurate. You are targeting any a that has _siblings_.

  • Neil says:

    Thanks for your great tutorial Tania! Ok so although I ended up cutting and pasting your code I loved being able to understand the how and the why of it all – I’m now a designer who is a bit deeper into code – if only everything was so well explained! 🙂 I’m using the nav on a website that is a side project of mine – is it ok if I link to this article and give you props?

  • lee says:

    Can you show me for sub menu items?

  • Graham says:

    Any chance this could support sub sub menu items?

    • john Fieldsend says:

      interesting technique, looking at integrating something simular to a wordpress mega menu and just seeing if I can adapt your code to support multilevel

  • Anonymous says:

    23263

  • mike says:

    Another great article. I have tried to center the nav objects, but can´t get it too work. Suggestions without using flex? Thank you.

  • sharmila says:

    this tutorial is absolutely amazing..I tried to make the nav bar sticky..but it didnt work.
    Any suggetions?

  • Alvin says:

    hello ma’am. I coppied the code from code pen, and and include the jquery library. But it still doesn’t work.

  • Shawn Simmons says:

    Thank you Thank you Thank you for this article. I have tried to get the menu items spread evenly across the nav bar but I can’t seem to get it to work. Any suggestions?

  • Vigneshwaran says:

    Please i want to know how to set drop downs when hover?. Let me know…

  • Melissa says:

    How would you drop down the submenu on hover?
    This is a great responsive menu!

    • Vigneshwaran says:

      I got the solution just do some change in the jQuery code then hover will work
      Just i added hover instead of click and then for clicking the submenu purpose i added the click.Just try it will 100%
      $(function () { // DOM ready
      // If a link has a dropdown, add sub menu toggle.
      $(‘nav ul li a:not(:only-child)’).hover(function (e) {
      $(‘nav ul li a:not(:only-child)’).click();
      $(this).siblings(‘.nav-dropdown’).toggle();
      // Close one dropdown when selecting another
      $(‘.nav-dropdown’).not($(this).siblings()).hide();
      e.stopPropagation();
      });

  • Russell Van Lieshout says:

    Good day,

    Absolutely FABULOUS! – Just what I was looking for… made my day – I was struggling with a few issues that your tutorial made crystal clear – Thanks so much! Might I suggest another topic??? A working, validated, recaptcha php contact form… maybe you know of a good tutorial???

  • HD Brink says:

    Thank you for a great tutorial.
    There is one bug I cannot figure out to fix.
    In mobile mode when we click on a sub-menu item the sub-menu will retract but the main menu does not close back to the hamburger button unless you click the X. Is there a fix for this?

  • Zuzana says:

    Hi Tania,
    thanks for this tutorial!

    I’m wondering, is there a work-around this part of the code:
    “?

    The accessibility checker does not like the broken link in that nav because it confuses screen reader users.

    Thank you.

    • Zuzana says:

      For future reference, this is how I solved it.

      Instead of using an empty link, I turned it into a button. However, as per the accessibility principles, you cannot have a button without any text either, so I gave it a ‘Button’ text which I made transparent with css. This way, the hamburger menu is still clickable, it works as it should, it is not an empty link nor an empty button.

      There might be a better way to do it but this is what worked for me.