Drop-Down Navigation with CSS only

Note: This is an update of an old popular post.

In this tutorial I show how to make drop-down navigation using CSS only. This tutorial starts with the code that I presented in the Simple Horizontal Navigation tutorial.

See the Pen Drop Down Navigation (CSS Only) by Lisa Catalano (@lisa_c) on CodePen.

Preferred Learning Method?

Code Snippets
If you just want the code and don’t need a description.

Watching a Video
If you like being shown how something works with some explanation watch the video.

Reading a description
If you learn better by reading, I am including the transcript of the video with corresponding code snippets embedded.

Code Only

See the Pen Drop Down Navigation (CSS Only) by Lisa Catalano (@lisa_c) on CodePen.

See the Pen Drop Down Navigation (CSS Only) by Lisa Catalano (@lisa_c) on CodePen.

Video

If you would like to follow along with the video:

Starting code here
Ending code here

Transcript

In this video I’m going to talk about how to create a Drop Down navigation menu using CSS only.

Before I start I should mention that this technique is not great for small screens or mobile websites. I’ll still approach this from a mobile-first perspective, but the solution I present is not perfect for small screens. I’ll tackle how to make it better in a future video.

I’m starting with code I presented in my Simple Horizontal Navigation tutorial. You might want to watch that video first if you don’t understand all of the code I am starting with.

If you want to follow along you can grab the code from my GitHub repository named “dropdown”. I’ll post a link.

I’m using the Brackets code editor so I can see the Live Preview as I work. You can see that this is a simple navigation that has different colors on the active item and also when you hover.

When I created this navigation I used a mobile-first approach and I started with a simple vertical navigation menu that changes to horizontal on larger screens.

HTML Changes

To add sub-menus, you simply need to add a nested unordered list inside the top menu item.

So if I want a sub-menu under Tutorials, I can go to that li tag and inside of it, insert another ul tag and some li tags for each menu item with a link inside. The syntax is just like the top level menu, just nested inside. I’m adding 3 menu items with some Emmet shortcuts.

I will also create a sub-menu for Newsletters.

          <ul>
            <li><a href="#">Tutorial #1</a></li>
            <li><a href="#">Tutorial #2</a></li>
            <li><a href="#">Tutorial #3</a></li>
          </ul>

Now this looks terrible on both the small screen and larger screen.

I’ll work on the small screen first.

CSS Changes for the Small Screen

The reason this is unreadable is because the sub-menus are rendering on top of the main menu. That is because there is a height property under .nav li. The list items with sub-menus are only given 40px which is not enough to contain the sub-menus.

If I remove it, the height will default to auto instead which means the browser will calculate the height needed based on its contents. Now we can see all the menu items.

The new problem is that the font size for the sub menu is larger than the top menu.

That is because I specified the font-size with the em unit. Ems are calculated based on the font-size of the parent. And when you have nested elements, that can cause a multiply effect. In this case, the sub menus are getting a font-size that is 1.2 times as large as the main menu.

So using ems can be frustrating when you are nesting things that specify a font-size.

There is another unit of measure called rems which stands for “root ems” and will always base its value on the root element instead of the parent. That means you avoid the cascade and the multiplier effect.

I will change the unit of measure to rem.

Now all the items are the same size.

However, rem does not work in IE8 and below so if you need to support that you need a different solution. You could use px instead.

I actually want the sub-menus to be smaller text, to help distinguish them from the top menus. So I am going to create a new selector for the sub menu li tags and then I can specify a different font-size.

If I specify the li that is inside of another li it will target the nested lists items only.

When I set it to 1em, you can see that the size is consistent.

/* Sub Menu code */
.nav li li {
  font-size: 1em;
}

But I’ll set this to .8em so that it is a little smaller.

I don’t like the text centered with this longer menu. This is coming from this text-align: center here on the unordered list. I want to leave this on the ul tag because I want this menu centered on big screens, but I will make the li tags have left alignment.

So I’ll add text-align: left to .nav li.

Then I need to add some padding so it doesn’t hug the left. If I add that to the list item, the border gets mess up, so I will move that line to the anchor tags.

(.nav a)

padding-left: 15px;

The final problem on the small screen menu is that the border is not showing up in between all the lines.

I have the border at the bottom of the list items, but since the bottom of the Tutorials or Newsletter list item is underneath the sub menu, the main menu list item doesn’t get the border.

Instead I’ll move that line of code down to the a tag also.

Now the small menu looks better.

CSS Down-Down Navigation on Larger Screens

So finally, I can tackle the main topic of this tutorial and that is how to make the sub-menus only show on hover on larger screens. That actually won’t take much time.

This next part of the code is happening inside of the media query. I am using a breakpoint of 650px because that is how much space the menu needs to be displayed horizontally on one line based on the width of the menu items. You can use ems or whatever breakpoint makes sense for your design.

This code inside the media query will only happen on screens that have 650 pixels or more.

The first thing I’ll do is set the positioning of the sub-menu to absolute, so that the sub menu is taken out of the flow of the document and doesn’t take up height anymore on the main nav bar.

I targeted the ul tags inside of li so that this only applies to the nested menus.

Now I want to hide these sub-menus. I can set the display property to none so the sub-menu does not display.

.nav li ul {
  position: absolute;
  display: none;
}

It is a little hard to see, but the bottom border is peeking out from the main navigation bar, so I want to turn that off on larger screens.

.nav a {
  border-bottom: none;
}

You can see the difference when I toggle this line on and off.

Next we want to display the menu when we hover on the parent menu. So we can target hover on the parent li and then the unordered list.

.nav li:hover ul {
  display: block;
}

The sub-menu is displaying on hover, but it is a horizontal menu. That might be what you want. If so, you could stop and style it so it is placed where you want and looks better.

But I want a vertical navigation.

The reason it is horizontal is because I am using display: inline-block on all li tags inside of .nav. That was to make the main navigation horizontal.

If I want this sub navigation to be vertical I need to target the sub li tags and display them as block;

.nav li ul li {
  display: block;
}

The width of sub-menu is a little short. That is because li’s have a width of 130px, but the background color is coming from the ul tag, which does not have a width set and therefore defaults to auto.

I want the sub-menu ul tags to have the same width as the parent li. If I say width: inherit it inherits the width of its parent, or the list item.

Now everything lines up nicely.

I don’t like that the text is now left-aligned. Remember I fixed that for the small screen.

If I add this code in the media query

  .nav ul  li {
    text-align: center;
  }

It still doesn’t look like the items are centered aligned because of the left-padding I added for the small screen menu. I’m going to ignore that briefly and will fix that in a moment.

Now all menu items are aligned to the center, including the sub-menu. It is not very noticable when all my text is the same width, but if I change one line, you can see it.

I want to center the top menu only. It is easy to target the child menus by adding more selectors, but I can’t do that on the top menu. I could add a class, or I can target the direct descendants of the nav class by using the child selector, which is the >.

See how when I add the child selector, only the top navigation is centered. Well, centered plus 15px of padding on the left.

I can use the child selector again to remove that padding.

  .nav > ul > li > a {
    padding-left: 0;
  }

When I’m using this child selector it will not target the sub-menu anchor tags because they are not direct descendents underneath the .nav div.

The padding-left of 15px will remain on the child menus.

Of course the styling of your menus depends on your actual content and design. You may not like the styles I’m using here and you should adjust to your own preference and design.

The main concept is the hidden menu that displays on hover. And that part of the code is right here. We hide the nested menu in the normal state, and we display it on hover. If you want it displayed vertically, you want to set display to block on the menu items.

The important thing to notice in this code is the selector. I am selecting ul tags inside of li tags. That means it is only targeting the nested sub-menus. It gets a little confusing looking at the different li and ul tags, so make sure you understand that. You could add classes to the sub menus to make things more clear if you want.

Browser Support

This will work in all browsers except IE8. That is because IE8 doesn’t recognize media queries. So we need to put all the code from the media query in the IE8 and below style sheet.

If you are using a pre-processor, you can avoid maintaining duplicate code, by putting the code in a separate file, and importing it into both the main style sheet and the IE style sheet.

So things are looking decent in both sizes.

What’s Next

There are more things to do to this to make it better. As I mentioned the small screen shouldn’t be displayed at the top of the page as is. On a phone, it would probably take up the entire screen. Also, some indicators that there are hidden menus would be useful here so the user knows they can hover to see more. Some transitioning on the drop-downs menus would be nice as well.

I plan on tackling these concepts in future tutorials coming soon.

Thanks for watching, please let me know if you have any questions.

Quick Coding with Brackets – Troubleshooting

(Full course listing here)

Extensions – Tutorial Highlights

In this final lesson I discuss how to troubleshoot issues you might have with Brackets.

0:00 – Ways to get help from the menu
1:10 – Reload without extensions
3:15 – Disable one extensions at a time

Thank you for watching!

I will continue to film updates when new features are released. I will also be releasing some reviews of more extensions in the future. Stay updated by following me here, through Twitter @lcatdesigns or on YouTube.