Centering a dropdown menu

By John Faulds

Recently, I needed to create a centered version of the Suckerfish dropdown menu and realised that some significant modifications were going to be needed. This is because the method for getting the top level list items to sit in a row, on the same horizontal plane, is to use float: left. However, when you float elements, you can't centre them unless you give them a width and use auto left and right margins.

If you can give each list item a fixed width and then calculate the overall width of all the list items, you could give the <ul> a width and centre it, but this isn't always a viable solution (e.g. the number of items may change or text size changes become a consideration). (Actually, I've had a rethink about this option and there's another example presented below.)

The menu's on the table

What I went with made use of a property of CSS that isn't utilised much due to it being unsupported by Internet Explorer. The significant changes are below.

#nav {
  background-color: #F00;
  text-align: center
}

#nav ul {
  display: table;
  margin: 0 auto;
}

#nav li {
  display: table-cell;
  position: relative;
  width: 11em
}

#nav li li {  display: block }

So what’s this display: table and display: table-cell about? Well, as the names suggest, using these properties makes the elements they’re applied to behave like tables.

So for #nav li, display: table-cell means that not only will the list items shrinkwrap to fit their content, but they’ll also appear on the same line, as cells in a table would. The display: table on #nav ul means that the <ul> is then only as wide as the contents of all the cells it contains (instead of being 100% of the available space) and it is able to be centered using text-align: center on the parent container.

Not on IE‘s table

As mentioned before, IE (including version 7) doesn’t support display: table, so the above code is only going to work in non-IE browsers. So, just for IE, we need to introduce another of the display properties:

#nav ul { display: inline-block }
#nav li { display: inline }
#nav a { display: inline-block }
#nav li:hover ul { top: 1.3em; left: 0 }

An inline block is one that is placed inline, on the same line as adjacent content, but behaves like a block. With IE, this only works for elements that are already inline, so what’s actually going on here is best described by Bruno Fassino:

The first line gives hasLayout. The second one resets the display as desired, without taking back hasLayout…

…Elements having both hasLayout and display:inline work similarly to the standard inline-blocks: they flow horizontally like words in a paragraph, are sensitive to vertical-align, and apply a sort of shrink-wrapping to their contents (which can include block elements.)

As mentioned, IE understands display: inline-block when applied to inline elements which is where #nav a { display: inline-block } comes in. The #nav li:hover ul { top: 1.3em; left: 0 } is required to move the dropdown menus below the parent list item (otherwise they appear over the top). Likewise, left: 0 is needed rather than left: auto because IE gets the positioning of the dropdowns wrong.

These extra rules are required for all Windows versions of IE which means conditional comments are really the only viable option in this instance. The completed example, which works in Firefox, IE6 & 7, Opera 8.5 & 9, Mozilla, Netscape, Safari and Konqueror, shows how all this is put together including the conditional comments and the other pieces of the CSS that I’ve missed so far.

(In case you’re wondering, I’m using the whatever:hover instead of the original Suckerfish article javascript to get IE6 to accept :hover on the list items.)

Everyone eating at the same table

Having completed this example I began to think about whether it would be possible to do the same thing but without the need for different code for Internet Explorer. What I came up with used the more familiar display: inline:

#nav li {
  display: inline;
  position: relative;
  width: 11em
}

#nav a {
  width: 6em;
  padding: 0 2.5em;
  line-height: 1.6;
  color: #FFF;
  background-color: #F00
}

#nav li li, #nav li li a { display: block; height: 1.5em }

#nav li li a { width: auto; padding: 0 }

#nav li:hover ul { top: 1.5em; left: 0 }

I won’t go into too much detail about the workings of this example because, while it works fine in Firefox and IE6 & 7, it fails rather badly in Opera 8.5 & 9 (it’s actually better in 8.5) on Windows which seems due to a bug in Opera which affects absolutely positioned (the dropdowns) elements inside inline (the top level list items) and is similar to a problem I had with image replacement and buttons. The solution there was to float the inline element but, of course, to do that means that the menu is no longer centred.

It also doesn’t look quite right in Mac browsers but as it doesn’t really have any practical, real world use due to its Opera limitations, I haven’t investigated ways to correct this. Of course, if anyone has any thoughts on how to make it work better cross browser, I’d love to hear them.

A slight rethink

The main advantage of using the display: table method is that you don’t need to set a width on any of the elements which means that you can set a bit of margin or padding on the top level list items and you could then add and remove list items (within reason) without having to change any of your CSS.

But, of course, for the effect that I’ve been trying to achieve with these examples, I’ve had to set a width so that the top level list items and their dropdown submenus are the same width. So, as Mike Cherim points out in the comments, there’s really no reason not to set a width on the <ul> and just centre it with margin: 0 auto.

#nav {
  float: left;
  width: 100%;
  background-color: #F00;
  text-align: center
}

#nav ul {
  width: 44.1em;
  margin: 0 auto;
  list-style: none;
}

#nav li {
  float: left;
  position: relative;
  width: 11em
}

All we need to do is give the <ul> a width equal to the combined total of the list items (a fraction more needed to prevent IE6 & 7 turning the last item to a new line) and then margin: 0 auto. This works fine in the IEs, but in other browsers, it means the red bar doesn’t extend the full width of the page anymore. Using overflow: hidden is my usual fix for getting non-IE browsers to contain the floats, but in this case hiding the overflow hides the dropdown menus, so instead I’ve floated #nav and given it a width of 100% (as floats will expand to contain other floats).

This example is actually the simplest, and most straightforward approach of them all and doesn’t require the use of any conditional comments (if you don’t count the one needed to give IE6 :hover behaviour on the list items).

So, it turns out I probably could’ve made things much easier on myself from the start. But I still think it’s useful to not only consider that there are often many different ways to approach different techniques with CSS, but also to realise that cross browser limitations (and that’s not always confined to IE) mean that some techniques can’t always be used.