I recently fixed a navigation issue at work that involved a tiered, CSS-driven dropdown/flyout menu. The menu worked properly using the CSS :hover selector but it did not support localization well and because of that, the CSS used "worse-case" hard-coded widths to ensure all locales were supported. In languages where the navigation text was much shorter than the "worse-case", the second tier of the menu used a width much too large for the text.
I designed a fluid-width, CSS-based tertiary menu system that contains no hard-coded widths or assumptions about sizing.
Let's go through the process of building this menu from scratch but first, let's define some goals
- No explicit widths
- Support in IE8, Chrome, Safari and Firefox
We will start by providing some barebones HTML for the menu
Now let's apply some basic CSS to create a horizontal menu
Now, let's add a secondary navigation menu for 'characters'. Considering accessibility concerns, a solid approach is to nest another list within the list item tag so a device that renders this without any styles will show a nice, tiered list.
But wait! Nesting a block-level element in a fluid container will cause the container to expand its width to contain the new element which in turns increases the width of the primary navigation element. There is no fix for that, sorry...
Okay obviously there is a fix and that fix is
This accomplishes two things:
- The HTML layout ignores the element when positioning everything
- The original positioning of the ul.secondary element is not changed
The secondary navigation list is supposed to be vertical and since our tertiary navigation list will be the same, change the styling for all lis and then override the one for the primary navigation bar.
Note that the selector for the primary navigation bar only targets children li elements and does not consider further descendants which is important because secondary and tertiary menus are all children of .primary
Now that we have a working secondary navigation level, let's investigate the tertiary level. Using the exact same approach as the secondary menu, we will nest another list within a secondary list item.
Add the same positioning to the tertiary list as the secondary list
Uh...that did not work as well as last time.
The problem is that .tertiary is positioned in relation to the same container as .secondary. It does not appear in the exact same position because if you simply assign an element position:absolute, it appears in the normal flow of the page.
The solution is to set the left position of the tertiary element to 100% which will push it back to the original horizontal position before applying the position:absolute
We're nearly there, but the tertiary navigation container is not correctly positioned. We can see this clearly if add some color into our navigation bar.
The top section of the tertiary container still appears relative to the original placement in the page (under the a tag) so simply give it a top:0 and it will position at the top.
which then results in:
The tertiary container is now correctly placed! Let's hook up the CSS hover functionality and add some more content
Everything looks great at first glance, but there are two issues with the tertiary navigation containers...
The first fix involves the white-space CSS property and setting this property to 'nowrap' causes the browser to ignore any space limitations on the parent container and allows its content to flow on a single line. This will fix our issue in modern browsers (IE8+)
The second fix is a positioning issue because the nearest relative positioned element for the second tertiary container to position itself to is the same as the first tertiary container - we just need to make the lis position:relative. However, this reintroduces the top positioning issue but we can easily fix that by setting the top property equal to the padding of the lis
The last remaining issue is a small offset for the top of the secondary navigation containers in relation to the parent lis in the primary navigation. The fix is super simple:
Whew...finally done! We now have a fluid, CSS-only navigation bar that supports secondary and tertiary levels of navigation!
I'd like to thanks Alexandra Atzl for pointing out a few issues with my original solution