Responsive Dropdown Navigation Bar

Responsive Dropdown Navigation Bar

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: 800px;
$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

Leave a Reply

Your email address will not be published.

Markdown is enabled in comments. If you would like to post code in your comments, please wrap it in a <pre><code>. HTML/PHP code must be escaped. Failure to do so will make me sad.

Example:

<pre><code>def print_hi(name)
  puts "Hi, #{name}"
end
print_hi("Tania")</code></pre>

26 comments on “Responsive Dropdown Navigation Bar”

  • Raffaello says:

    Great tutorial, clean and simple to understand.
    May I ask you if there is a way to have the mobile menu close when you click outside of it (like the desktop menu)?
    Many thanks.

    raf

  • gigio says:

    Hi Tania,

    thank you so much for your great article! It helped me a lot with my site redesign project.

    Do you have any good code to make this navbar sticky?

    Tried this but on mobile the menu covers the content instead of pushing it down like in the original code:


    .navigation {
    height: 70px;
    background: #8BC34A;
    position: fixed;
    width: 100%;
    top: 0;
    z-index: 1;
    }

    body {
    padding-top: 70px;
    }

    Thanks!

    • Tania says:

      In this case, you would put margin-top: 70px; on the article, or whatever div/section you had directly below the navigation that contains your main content.

  • Linda says:

    Hello Tania! I dont know if you got my email before, so I will try again. I really love your design, but it does not seem to work and respond on touch on my mobile. Here is one that Iยดve built that works on my mobile, but only problem is that it doesn not take the logo with it when it changes to the hamburgermeny. Is there anyway you could implement the code here into your tutorial, so it responds to touch on the mobile? Or could you help me understand how I can get the logo to come next to the hamburgericon? Example

    Im not so good at programming and really like your tutorial and your beautiful design. Heres my example: http://www.ljusporten.se/tania/tania.html. It works on the computer in mobile size. If you dont have time, could you please just let me know that, so I can try to figure out the code myself asap..? Kind regards Linda ร–hrn

  • Linda says:

    Hello Tania! Thank you for your beautiful meny ! It works perfect on my computer, also in mobile size. But….. it does not work on my mobile device, an android. The hamburgermeny is already open when the page loads, and it does not close or react on click. Also the nested link “Services” (it does not display the arrow on my mobile), does not react or open on touch. Wherever I click the on the links in the menu it does not close. I really love this one, this is the sixth tutorial Ive build, that works like I want on my computer. Can you please help me fix it for mobile device? It does not seem to respond on touch at all. Heres my demo: http://ljusporten.se/tania/tania.html I took a screenprint from my mobile: http://ljusporten.se/tania/SC20160824-155521.png (I rotated my mobile just to print a bigger picture, its still the same problems as above.) /Linda

  • Peter says:

    Hi Tania,

    This tutorial was remarkably helpful! I’ve learned so much from it, and I can’t thank you enough.

    However, I hate to say, I just can’t seem to get the final product to work…! I must be missing something; even when I copy and paste the CodePen you put together for Mark into a brand new CodePen just to test it, I get the same result, which is to say the dropdown doesn’t drop down and the hamburger doesn’t become an “X” or open the menu.

    Like I said, I must be missing something but I just can’t see what…! Do have any idea why this might be?

    • Tania says:

      If you’re just copying and pasting my code into a new pen, you’re not copying jQuery over, which is in the Codepen options. Add the jQuery library and it will work.

  • Daniel says:

    This is amazing. I have a bootcamp coming up next week and I really wanted to understand how a navigation like this works. Also this made me realize switching from LESS to SASS is going to be way easier than I thought. I knew exactly what the SASS notation was doing. Thank you.

  • jeff brooks says:

    Tania

    Thank you very much – I am just now getting back into coding sites at the request of some old friends – a thing or two or a thousand has changed over the past 3-4 years. I stumbled across this while looking for information on responsive menus – thank you very much I really enjoyed it. And your responses to everyone who writes in are equally kind.

  • Jorge Moller says:

    Hello Tania,

    First of all thanks for this awasome lesson, it really helped me a lot!!. Just one quick opninion that may help others, when creating hamburguer menu, you wrote “Toggle the .active span.” followed by this code:

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

    I think you meant to write something like this, otherwise the animation will not work (at least for me it did not):

    $(‘#nav-toggle’).click(function() {
    $(this).toggleClass(‘active’);
    });

    Anyway, keep it up the good work, again, this put me in the right direction to start building my own css framework so thanks!! ๐Ÿ˜‰

  • Mark says:

    I’ve been trying to replicate your example and follow along since it was first posted in early October and read it on Reddit.

    However, I am having a bit of trouble and I don’t know if it has to do with the jQuery. Another, is that I cannot get the content: ‘ ?’; to appear next to “Services” in the nav bar. It will not drop down or toggle when clicked upon.

    I’ve installed Ruby 2.2.3, SASS, linked to Bootstrap CDN website in the header, and made sure my .js file is in the correct file location and linked in the HTML header.

    Am I missing proper jQuery installation?

    • Tania says:

      Hi Mark, I’d love to help figure out what the problem is. Is there any chance you can make a pen or send me your code so I can see what’s going on?

      Also, I noticed that you said “my js file is in the header”. It’s important that your JavaScript file go in the footer, below jQuery.

      So, in the footer you should have one script src pointed at jQuery, and your custom JavaScript below it.

      Additionally, this navigation does not require Bootstrap to function.

      • Mark says:

        Sorry for the delay, I posted late and had to end the night to get up for work.

        Here is the CodePen link with the code I was using.:

        http://codepen.io/anon/pen/epjXEe

        Pretty much the same code in your tutorial (I strayed from copy/paste and tried to hand write as much as possible). I’m sorta guessing it has to do with proper jQuery linking.

        The problem is, I cannot get that triangle to render next to services and activate the drop down menu using jQuery. I moved the script tags down into the footer as you suggested, but I’m still getting the same result that the Pen is rendering.

        P.S. If I do not link to Bootstrap, the X and Menu option does not sit right. I commented out the body jQuery and the Bootstrap link.

        • Tania says:

          Hi Mark,

          The reason there’s a space at the top of the screen is because of browser consistency. If you add “Normalize.css” to your pen, it will take care of this issue, and prevent headache on further projects. Additionally, you can simply type

          body {
            margin: 0;
          }

          in your CSS to take care of that issue. (I would always recommend using Normalize, though!)

          First of all, you have your nav-toggle showing on desktop, which shouldn’t be the case, since the menu is already there. So I added a display: none to mobile view.

          .nav-mobile {
            display: none;
          }

          You want to change all your links from”#” to “#!” to prevent them from doing any action on click. Going to “#” will reload the page at the top of the screen, wile “#!” will do nothing.

          You already know that jQuery is loading properly, because the navigation is toggling on click, and the mobile menu appears when you click the hamburger.

          The reason your dropdowns aren’t working is this: My code is ul li a:not(:only-child):after. This means “any list that has more lists” should have the arrow.

          Here is your code:

          <li><a href="#!" rel="nofollow">Services</a></li>
          <li>
            <ul class="nav-dropdown">
              <li><a href="#" rel="nofollow">Web Design</a></li>

          Here is the correct code:

          <li><a href="#!" rel="nofollow">Services</a>
            <ul class="nav-dropdown">
            <li><a href="#" rel="nofollow">Web Design</a>

          Can you spot the difference? You ended your list and started a new one for “Web design” and so on. This needs to be a new list nested inside of your existing list.

          Additionally, I would look into the way I’m nesting my SCSS. You’re simply writing vanilla (regular) CSS, like this:

          nav {}
          nav ul {}
          nav ul li {}

          For Sass, you should be writing it like this:

          nav {
            ul {
              li {}
            }
          }

          Finally, on a Codepen, you can choose “jQuery” in the options on the JavaScript panel and load it from there.

          I hope that helps. Here is an updated version of your pen.

  • Nilson Gaspar says:

    This is very helpful Tania, as whole the material in your page, mainly because the way you explain, is clear to any level of knowledge, but even more for beginners, like myself. I wonder if you would consider make a tutorial to do a hamburger menu slide navbar, like the one from the Jekyll theme Poole. I am currently trying to code one myself, with not much success, but i will build it (instead of using Poole’s i’d rather learn how to build one for future projects).

    PS: i’m the guy that loves you, from reddit ?