Always centred CSS dropdowns

Centring elements horizontally or vertically often works in mysterious ways and one of the seemingly simple layout problems that can make CSS so frustrating to use. There are several techniques to cope with different needs but combining absolute positioning with unknown dimensions usually conspires to create absolute developer misery. This is a battle-tested dropdown menu able to cope with an unknown number of option columns and always centred relative to its toggle using only IE8-compatible CSS.

The markup

<div class="dropdown">
  <a class="dropdown__toggle" href="">Mobile Devices</a>
  <div class="dropdown__panel">
    <div class="dropdown__panel__wrapper">
      <div class="dropdown__panel__column">
        <ul class="dropdown__panel__menu">
          <li><a href="">Apple iPhone 5s</a></li>
          <li><a href="">Apple iPhone 5c</a></li>
          <li><a href="">Apple iPad</a></li>
          <li><a href="">Apple iPad mini</a></li>
        </ul>
      </div>
      <div class="dropdown__panel__column">
        <ul class="dropdown__panel__menu">
          <li><a href="">Google Nexus 5</a></li>
          <li><a href="">Google Nexus 7</a></li>
          <li><a href="">Samsung Galaxy III</a></li>
          <li><a href="">Samsung Galaxy Note</a></li>
        </ul>
      </div>
    </div>
  </div>
</div>

There’s nothing remarkable about the markup, it’s a standard nested navigation structure, only with the addition of an extra wrapping element. I’ve used the BEM naming convention to avoid nesting CSS selectors and to make the code as self-documenting as possible. For extra design embellishments such as the outlined arrow it’s sensible to add an extra wrapper.

The CSS

Firstly, to use multiple columns without specifying the widths of them or any parent elements requires the use of table layout which I’ve written about in detail before. Table columns cannot be collapsed onto multiple rows and so any parent elements without a defined width will be sized to contain its children.

Aligning the dropdown as centred relative to the toggle is achieved in three steps:

  1. The dropdown ‘root’ element creates a new containing block by declaring position: relative and shrink wraps the dropdown toggle by being displayed as an inline block.
  2. The dropdown ‘panel’ is positioned absolutely and shifted 50% from the left edge of its containing block (the root element).
  3. A negative margin of 50% is applied to the dropdown panel ‘wrapper’ and because the percentage value is calculated relative to the width of its parent (the panel) it is shifted perfectly back into the centre.

The complete CSS skeleton for the menu is below. I haven’t included any visual embellishments such as the shadows or arrows, just the basic layout.

.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown__toggle {
  display: block;
}

.dropdown__panel {
  position: absolute;
  top: 100%;
  left: 50%;
  display: none;
}

.dropdown:hover > .dropdown__panel {
  display: block;
}
.dropdown__panel__wrapper {
  position: relative;
  left: -50%;
  display: table;
}

.dropdown__panel__column {
  display: table-column;
}

.dropdown__panel__menu {
  width: 100px;
  margin: 0;
  padding: 5px 10px;
  list-style: none;
}

And that’s it, no need to hard code any widths and certainly no need to bring Javascript into layout concerns. Check out the full example source for some fun with CSS arrows ;-).

View the source on JSFiddle