Grow your CSS skills. Land your dream job.

Targeting Menu Elements with Submenus in a Navigation Bar

Published by Guest Author

The following is a guest post by Ray Messina. Ray was interested in sharing this technique as a way to pay forward things he's learned from this site in the past, which is awesome. You might be aware of the jQuery .has method, which allows you to select an element if it has any of another selector as a descendant. There is sadly no such selector in CSS yet. But if you know a little something about the HTML structure, you can use a combination of positional selectors to mimic it. Ray will explain.

Recently, I was working on a dropdown navigation bar and wanted to differentiate the menu items which contained sub menus from those that didn't. I wanted to be able to do this automatically, without relying on JavaScript or having to add classes to the markup. I just wanted it to work as if the item just knew it had a sub menu or not.

The Need for Navigational Hints

As you probably know, menus are lists of links and, as such, it is standard practice to mark them up as <ul>s. By extension, drop down menus are merely nested <ul>s. Dropdowns are a common component of modern (and not so modern) web design. Using pure CSS, one can style the upper level of a navigation menu any which way, and hide the sub levels so that they are revealed only when the visitor hovers on the appropriate area.

Many designers are satisfied with leaving as that. However, from a UX/UI point of view, this is lacking as it leaves the user having to explore the entire menu to find which sections contain additional navigation links. This means visitors will either waste their time looking and get frustrated for doing so or miss areas of your site all together! That's no good at all.

The common way to address this issue, is to simply add a class to the <li>s that contain <ul>s (sub menus) so that we can style those items differently from those that do not contain sub navigation. Pretty simple, but also pretty tedious and not particularly graceful.

Another way, assuming that upper level items are not coded as links, is to use that difference in tags as leverage. That is, create faux links: style <span>s that's are direct children of the <li> to indicate that there are more links that follow and, by contrast, style anchor tags as not having additional sub items. This is handy if your drop-down is simple and you can structure it specifically that way. It's cleaner in that you don't have to add "submenu" classes to your markup but, just as before, it still requires that you structure the content of your list manually or that your CMS can foretell which list items will contain other <ul>s as sub menus.

Doing It Automatically!

I had come across other methods of automatically styling list items which contain other lists, but they employed absolute positioning and a pseudo element off the child <ul>. While clever, the CSS calculations can be tricky at times or the method may be entirely impossible to implement depending on what other techniques you have used to lay out your menu and/or position your submenus, or the overall effect you desired to achieve.

A better way

Ideally, it would be great if there was a CSS selector that would let us query if an element contained another kind of element as a direct child, something akin to jQuery's .has() method.

We can can achieve nearly the same thing with li a:first-child:nth-last-child(x) { }.

The key is having an expected child count (HTML element planned parenthood?). Most likely it will be two elements: the anchor and the <ul>, though one can tweak this technique to work for any number or child elements, as long as you have a regular pattern.

Here is a quick example. The markup is just your standard nested UL, but note that I have only used one class in the HTML, on the root <ul>. Test this out yourself, add any number of nested lists at any level!

Creating a Dropdown Demo

Let's put this idea to the test!

The HTML: Keep It Clean

<nav>
  <ul class="nav">
    <li><a href="#">About</a></li>
    <li><a href="#">Portfolio</a>
      <ul>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
      </ul>
    </li>
    <li><a href="#">Resume</a>
      <ul>
        <li><a href="#">item a lonng submenu</a></li>
        <li><a href="#">item</a>
          <ul>
            <li><a href="#">Ray</a></li>
            <li><a href="#">Veronica</a></li>
            <li><a href="#">Bushy</a></li>
            <li><a href="#">Havoc</a></li>
          </ul>
        </li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
      </ul>
    </li>
    <li><a href="#">Download</a></li>
    <li><a href="#">Rants</a>
      <ul>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
      </ul>
    </li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>

The CSS

Some general styling to bring in the sexy:

nav {    
  display: block;
  text-align: center;
}
nav ul {
  margin: 0;
  padding:0;
  list-style: none;
}
.nav a {
  display:block; 
  background: #111; 
  color: #fff; 
  text-decoration: none;
  padding: 0.8em 1.8em;
  text-transform: uppercase;
  font-size: 80%;
  letter-spacing: 2px;
  text-shadow: 0 -1px 0 #000;
  position: relative;
}
.nav{  
  vertical-align: top; 
  display: inline-block;
  box-shadow: 
    1px -1px -1px 1px #000, 
    -1px 1px -1px 1px #fff, 
    0 0 6px 3px #fff;
  border-radius:6px;
}
.nav li {
  position: relative;
}
.nav > li { 
  float: left; 
  border-bottom: 4px #aaa solid; 
  margin-right: 1px; 
} 
.nav > li > a { 
  margin-bottom: 1px;
  box-shadow: inset 0 2em .33em -0.5em #555; 
}
.nav > li:hover, 
.nav > li:hover > a { 
  border-bottom-color: orange;
}
.nav li:hover > a { 
  color:orange; 
}
.nav > li:first-child { 
  border-radius: 4px 0 0 4px;
} 
.nav > li:first-child > a { 
  border-radius: 4px 0 0 0;
}
.nav > li:last-child { 
  border-radius: 0 0 4px 0; 
  margin-right: 0;
} 
.nav > li:last-child > a { 
  border-radius: 0 4px 0 0;
}
.nav li li a { 
  margin-top: 1px;
}

Then the magic happens:

.nav li a:first-child:nth-last-child(2):before { 
  content: ""; 
  position: absolute; 
  height: 0; 
  width: 0; 
  border: 5px solid transparent; 
  top: 50% ;
  right:5px;  
 }

That's essentially the active ingredient of this technique. For this example, I used the :before pseudo element off the anchor element to draw the arrows. The pseudo element is not necessary to the technique. I could have just as easily changed backgrounds on the anchor itself instead; you can make it do most anything you want once you have targeted the element.

Finally, just to polish it off, some positioning and arrow styling CSS code.

/* submenu positioning*/
.nav ul {
  position: absolute;
  white-space: nowrap;
  border-bottom: 5px solid  orange;
  z-index: 1;
  left: -99999em;
}
.nav > li:hover > ul {
  left: auto;
  margin-top: 5px;
  min-width: 100%;
}
.nav > li li:hover > ul { 
  left: 100%;
  margin-left: 1px;
  top: -1px;
}
/* arrow hover styling */
.nav > li > a:first-child:nth-last-child(2):before { 
  border-top-color: #aaa; 
}
.nav > li:hover > a:first-child:nth-last-child(2):before {
  border: 5px solid transparent; 
  border-bottom-color: orange; 
  margin-top:-5px
}
.nav li li > a:first-child:nth-last-child(2):before {  
  border-left-color: #aaa; 
  margin-top: -5px
}
.nav li li:hover > a:first-child:nth-last-child(2):before {
  border: 5px solid transparent; 
  border-right-color: orange;
  right: 10px; 
}

Here is the whole thing in action at CodePen:

See the Pen Marking sub-menued items (sexy version) by Ray Messina (@RayM) on CodePen

As you probably guessed, you could also use other selector/selector combinations such as :only-child, :first-child:last-child, :first-child:not(:last-child) and the like. But I have found that :nth-child(x):nth-last-child(x) gives the most flexibility and serves as a built-in fallback (as it allows us to target the element directly, rather than by exclusion), and there is little cross browser support advantage gained by using the other selector/selector combinations.

That's it. Simple, graceful and totally automatic; the way it ought to be. Support for this is almost universal, except for IE, which eats glue and only offers support in IE9+. At the time of writing, estimated global support for these selectors, as used, was roughly 87% which is not too bad.

Comments

  1. louis
    Permalink to comment#

    nice thank you for this

  2. Nope
    Permalink to comment#

    Does not work in FF 25 Beta, the dropdown vanishes immediately when I move the cursor (not matter what direction)

  3. Anders Tolborg
    Permalink to comment#

    In my opinion this is horrible code. It’s hard to read for future maintainers, and it’s not very performant. A better solution would be to alter the markup and add some classes to the various elements and thereby decouple the HTML and CSS from each other. However, I understand that not everyone has this luxury, and then this solution is probably fine.

    • Michael
      Permalink to comment#

      I agree.

      As much as I love the first-child & nth-child, I often find I refactor ( if I can ) with actual classes to help with readability. Making the code easier to understand for other people working on the project is much more beneficial I believe.

      I only use pseudo classes when I am rapid prototyping a layout, or working in an environment when I can’t easily access the HTML.

    • I’m probably the biggest markup purest out there right now and I’ve been abusing nth-child like a madman for years, but I’m finally starting to turn a corner with it. Classes are abused (vertical divider code), nth-child is abused (above), but we need some nice, legible, middle ground to become the default, and I don’t think it’s ugly-ass OOCSS.

  4. Permalink to comment#

    Clever, clever… I think everything would be so much easier with a “parent” selector.

  5. I really like the way the arrows rotate 180 degrees when we navigate to the child menus. Great & Smart work.

  6. A clever solution…but again is it over automation…I am willing to bet that most CMS’s could be configured to output a class on a parent nav element that contains sub nav elements…saving a lot complex CSS wizardry…

    Also using hover on the “li” element is not as accessible as using it on the “a” element…as you can have a “focus” state on an “a” element that matches the “hover” state…this is helpful for keyboard users who tab around your web site…Someone correct me if I am wrong but you can not focus into an “li” element…

    Navigation elements should navigable both with a mouse and a keyboard…also touch has to be considered now as well…

    • Permalink to comment#

      Also using hover on the “li” element is not as accessible as using it on the “a” element…as you can have a “focus” state on an “a” element that matches the “hover” state…this is helpful for keyboard users who tab around your web site…Someone correct me if I am wrong but you can not focus into an “li” element…

      This is exactly what I was thinking when I read: “Another way, assuming that upper level items are not coded as links, is to use that difference in tags as leverage. That is, create faux links…”

      I’ve done this before thinking that I was doing a good SEO practice that says that <a href="">‘s should be used for “real” links since they have very good SEO weight, to later realize I had completely killed the accessibility of the main nav items because visitors using their keyboard now cannot TAB through elements that are not <a href="">‘s.

      Now I know to never do that again.

    • jamie
      Permalink to comment#

      Correct, you can not focus non “a” elements – had a big piece of work to do on this recently.

  7. Ryan
    Permalink to comment#

    Two things:

    -This does not work in the latest Chrome.
    The sub-menu of the item in Resume pops up, but disappears when you try to hover over it.

    -This is not very elegant to look at code wise.

    • Permalink to comment#
      • Could you take a screencast of how it “doesn’t work”? I’ve heard another person complain about this as well but haven’t been able to replicate it. I also run the latest Chrome.

      • It’s a trick. It’s a clever way to select an element when it’s not alone. I’m sorry it doesn’t live up to your elegance standards. In my experience, understanding ideas like this is useful in day to day CSS-foo.

    • Permalink to comment#

      Move your mouse very slowly from top downwards. I haven’t personally tested in Chrome, but in Firefox, it disappears at a certain point. In Firefox at least, this is due to the “margin” values in the CSS, in other words you end up hovering over an empty space that is not part of the menu (:hover is no longer maintained). In Firefox, if you flick your mouse down fast enough, the browser doesn’t have enough time to make it disappear completely and so the :hover state is maintained, and no issues.

      This sort of issue was very irksome during the days of IE6, but rarely happens any more. It can still happen though, as demonstrated here. Remove the offending margin(s) and you shouldn’t have an issue.

      I’ll let Ryan elaborate on the Chrome issues he was experiencing.

    • jamie
      Permalink to comment#

      you could just consider the code to highlight the fact it has children and use another method for the show/hide option of the drop down I guess..

    • Ryan
      Permalink to comment#

      Woops! I was incorrect about the latest Chrome bit, turns out my Chrome updater is actually faulty :(
      I’ve re-installed Chrome and this works fine.

      Regarding the elegance, I understand the CSS trick fine…it just feels like if I passed this on to someone else didn’t understand the trick they might have a hard time figuring out what this CSS is doing, perhaps some CSS comments would suffice :)

  8. Permalink to comment#

    Another option is to style the <ul> to indicate there is a dropdown present in this element.

    Per default you hide the dropdown <li>s but not the <ul> itself. When it (or its parent) is not being hovered, the <ul> has the styling that indicates a dropdown element, when it’s being hovered, the dropdown displays normally.

  9. Permalink to comment#

    This is a very interesting way of accomplishing dropdowns (which we know have usability issues), but yet is something we can’t really fully live without, like our pal IE ><.

    This is great life-saver if someone has to build a dropdown menu and cannot use JavaScript. I’m saying this because a few months ago I had to retrofit a website with responsive design and I wasn’t allowed to use JavaScript for the menu in small screen devices. Thank you Aaron Gustafson :)

    Note that for me in Firefox 24.0 the dropdowns fail 3 out of 5 tries to hover over them. It must be a small positioning issue, but still, they are failing at this moment I’m typing this.

    Also, I don’t see an issue in assigning classes to the <li>‘s that contain dropdowns, you can add those classes with JavaScript if you want to keep the original markup clean, or if for whatever reason you can’t access the markup.

    Using JavaScript for dropdowns (not necessarily for menus) is something we all here do all the time, I personally don’t think I’ll be doing a CSS-only dropdown any time soon, but this article is certainly my go to tut if I ever encounter such gnarly situation.

    I really like how the arrows switch up/down, very descriptive and visually helpful.

  10. Alex Kempton
    Permalink to comment#

    This is a really clever hack, but adding a class really isn’t that tedious in a situation like this. You’re trading off clean markup for messy CSS.

    These days I find making websites a balancing act between keeping my markup clean and keeping my CSS clean. More and more I find that I prefer to keep my CSS clean by using a few extra elements/classes in my markup.

  11. Ze Grammar Nazis
    Permalink to comment#

    Uh, guys… “Targeting” is spelled with only 2 “T’s.”

  12. Richard Denton
    Permalink to comment#

    I’ve gotta jump on the “why not use a class” bandwagon.
    Sure, it’s clever and awesome CSS, but realistically, a lot of future maintainers won’t fully understand it.

  13. Permalink to comment#

    Wow. That went controversial.

    I suppose I was being ‘old school. The concept is about marking items with additional content w/o altering the HTML; potentially this technique be used in situations other than menus as well. I have had people come to me with only access to or knowledge of CSS, but not the actual CMS template; in which case this could be a lifesaver, for example. CSS3 selectors do offer an amazing amount of control for targeting elements and I find it useful( depending on the situation, as always).

    I often use “opt-in” classes myself, but the idea of the CSS ‘reacting’ to the markup, especially when I have seen things like:<li class='menu sub long pink current other item item-15'>...</li>, appealed to me.

    On the aesthetic side of this particular demo, this addresses the ‘dead area’ issue:
    .nav > li li:hover > ul {
    /* margin-left:1px; replace this rule with the follwing */
    border-left: 1px solid #fff;
    }

    Am updating that on my code pen.

  14. paulob
    Permalink to comment#

    Good idea Ray, that’s an interesting technique.

    Whether adding a class is easier is a valid point but as far as the CSS goes the technique is valid, informative, and instructional, and as far as I know never been used before for this situation.

  15. Diego
    Permalink to comment#

    Looks like this? ;)

    http://cdpn.io/edrHC

  16. caspian
    Permalink to comment#

    Ok, I’ll just plunge in here. :-) Is there anyone here who’d be willing to give me a brief explanation of how the developers got the nav bar on this page https://www.att.com/olam/passthroughAction.myworld?actionType=Manage to function the way it does, in exchange for a promise of a free beverage or two of their choice some day?

    I’m specifically trying to duplicate the way the horizontal subnav functions. I like how each one is activated upon hover and stays displayed until you hover over another li and the subnav for that appears and stays displayed. For instance, when you hover over ‘Shop’ in the orange menu, it opens the subnav with WIreless, Bundles, Digital TV, etc. Then when you hover over ‘myAT&T,’ it displays the items Overview, Bill & Payments, etc. It’s good usability because it reduces the number of clicks the user has to perform to navigate to different sections. I’m trying to duplicate that effect for an app I’m building as a way to reduce repetitive tasks for our users (some on IE8).

    Even just a brief outline without all the details, like whether you think it’s mostly powered by jquery, would be much appreciated! Thanks in advance!

    • Bob
      Permalink to comment#

      Think of it as 4 menus. They have the Primary Menu (Shop, My ATT, Support). They then have a menu for each item in the Primary Menu (so a Shop menu, My ATT menu, and Support menu). These menus are hidden with CSS. They then use jQuery to add a class to whichever menu should be visible. That class then unhides that menu.

  17. Olomee
    Permalink to comment#

    Thanks !
    Css loves when does all the trick.
    Sweet !

  18. I got one more method that is to use the following Jquery
    $(‘.nav’).find(‘li’).has(‘ul’).addClass(‘parent’);
    Then you can use :afteror :before to add something on it or to customize in different mannor.

  19. Jeremy S
    Permalink to comment#

    Really? Everyone’s complaining about the relative maintainability of one line of css?

    .nav li.has-dropdown a:before {
    

    vs

    // menu dropdown indicator
    .nav li a:first-child:nth-last-child(2):before {
    

    or even

    // menu dropdown indicator;
    // if you really want to know...
    // @source: http://css-tricks.com/targetting-menu-elements-submenus-navigation-bar/
    .nav li a:first-child:nth-last-child(2):before {
    

    I’ve seen much crazier things go completely unchallenged here.

    Whatever happened to bandwidth conservation? Especially for big menus, this trick would remove a lot of “unnecessary” characters.

    • Adam
      Permalink to comment#

      That is a very fair point. I, too, have seen much wilder code be accepted by the general design community without so much as an objection. I applaud this technique. It’s working with what we have in the tool box. I’ve read a few comments about getting a parent selector – is this something that will happen? In certain ways it would be a good thing – such as in this example – in others it could be a pain if used irresponsibly.

  20. avni
    Permalink to comment#

    I added a link to an item and it replaced current navigation bar with new page. How can i open a link under the same navigation bar?

  21. Sudheer
    Permalink to comment#

    Its working working on IE 8 , any work around..?

  22. how can i do for active menu?

  23. Sidney Lindner

    Very neat and clean. I’m wondering if anyone has adapted this to use as a WordPress menu?

  24. bossbond

    many many thanks

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".