HTML Dog

Skip to navigation

Son of Suckerfish Dropdowns

By Patrick Griffiths and Dan Webb.

The original Suckerfish Dropdowns article published in A List Apart proved to be a popular way of implementing lightweight, accessible CSS-based dropdown menus that accommodated Internet Explorer by mimicking the :hover pseudo-class.

Well now they're back and they're more accessible, even lighter in weight (just 12 lines of JavaScript), have greater compatibility (they now work in Opera and Safari without a hack in sight) and can have multiple-levels.

Single-level Dropdowns

Right. Let's not beat around the bush. The initial HTML we're dealing with will look something like this:


<ul id="nav">
	<li><a href="#">Percoidei</a>
		<ul>
			<li><a href="#">Remoras</a></li>
			<li><a href="#">Tilefishes</a></li>
			<li><a href="#">Bluefishes</a></li>
			<li><a href="#">Tigerfishes</a></li>
		</ul>
	</li>

	<li><a href="#">Anabantoidei</a>
		<ul>
			<li><a href="#">Climbing perches</a></li>
			<li><a href="#">Labyrinthfishes</a></li>
			<li><a href="#">Kissing gouramis</a></li>
			<li><a href="#">Pike-heads</a></li>
			<li><a href="#">Giant gouramis</a></li>
		</ul>
	</li>

	<!-- etc. -->

</ul>

A good wholesome structured unordered list.

To set things up we need some basic styling:


#nav, #nav ul {
	padding: 0;
	margin: 0;
	list-style: none;
}

#nav a {
	display: block;
	width: 10em;
}

#nav li {
	float: left;
	width: 10em;
}

Note that you need to specify a width in the #nav li selector or else Opera will chuck a wobbly. Also remember that because we're floating things, the content underneath the dropdowns also needs to be cleared (clear: left).

We obviously need to hide the lists that we want to 'drop down' but to make things as accessible as possible we need to avoid using display: none, which, as is commonly mentioned in image replacement write-ups, hides elements from some screen readers. You might think that there are a multitude of ways to deal with this, but having exhaustedly experimented with widths, heights, margins, top and clip across a large number of browsers, the best solution (accommodating multiple level lists anyway) lies in manipulating the left property.

The CSS specs say that top, right, bottom and left values should offset an absolutely positioned box from its containing block. But unfortunately Opera decides to offset absolutely positioned boxes in relation to the page and that's why the original Suckerfish Dropdowns didn't work on Opera - because they relied on the top and left properties with explicit lengths.

So instead of display: none we use left: -999em to propel the dropdown list out of view and then left: auto (rather than left: 0) to bring it back:


#nav li ul {
	position: absolute;
	width: 10em;
	left: -999em;
}

#nav li:hover ul {
	left: auto;
}

And that will sort out everything for those browsers that fully support the :hover pseudo class, but for Internet Explorer we need to set the Suckerfish JavaScript loose:


sfHover = function() {
	var sfEls = document.getElementById("nav").getElementsByTagName("LI");
	for (var i=0; i<sfEls.length; i++) {
		sfEls[i].onmouseover=function() {
			this.className+=" sfhover";
		}
		sfEls[i].onmouseout=function() {
			this.className=this.className.replace(new RegExp(" sfhover\\b"), "");
		}
	}
}
if (window.attachEvent) window.attachEvent("onload", sfHover);

Basically, this applies the 'sfhover' class to li elements in the 'nav' id'd ul element when they are 'moused over' and removes it, using a regular expression, when 'moused out'.

So now we've got the Suckerfish pumping out new classes, the next step is to simply duplicate the :hover selector with 'sfhover' class selectors:


#nav li:hover ul, #nav li.sfhover ul {
	left: auto;
}

And there you go. Your standard single-level dropdown menu.

Multi-level Dropdowns

The original Suckerfish Dropdowns article covered only single-level dropdown menus, but with a bit of an extension of the cascading logic, it is quite possible to create multi-level dropdowns with CSS too. And, unlike the original Suckerfish JavaScript code, the 'sfHover' function now applies the behaviour to all of the descendent li elements of 'nav' rather than just the direct children so now multi-level dropdown menus are quite possible in Internet Explorer too.

So, to get started, let's say we're dealing with a list structure with more levels like this:


<ul id="nav">
<li><a href="#">Percoidei</a>
	<ul>
		<li><a href="#">Remoras</a>
			<ul>
				<li><a href="#">Echeneis</a></li>
				<li><a href="#">Phtheirichthys</a></li>
				<li><a href="#">Remora</a></li>
				<li><a href="#">Remorina</a></li>
				<li><a href="#">Rhombochirus</a></li>
			</ul>
		</li>
		<li><a href="#">Tilefishes</a></li>
		<li><a href="#">Bluefishes</a></li>
		<li><a href="#">Tigerfishes</a></li>
	</ul>
</li>

<li><a href="#">Anabantoidei</a>
	<!-- etc. -->
</li>

<!-- etc. -->

</ul>

There are a few things we need to add to the single-level method. Firstly, the third-level list (in this example 'Echeneis, 'Phtheirichthys' etc.) needs to drop down to the side of the corresponding list item (in this case 'Remoras'), so we need to add this rule, which will apply to all dropdowns after the first one:


#nav li ul ul {
	margin: -1em 0 0 10em;
}

Because we can't explicitly specify the top of the absolutely positioned boxes, they will sit below the line of the hovered list item, which is why the top margin of the next level of lists needs to be set to -1em. But this won't pull the menus up far enough the be in line with the corresponding list item because by default line heights are greater than 1em (usually 1.2em), so we need to add a little something to the initial ul rule set:


#nav, #nav ul {
	padding: 0;
	margin: 0;
	list-style: none; 
	line-height: 1;
}

Due to the cascading effect whereby upon the second level list being displayed, the third level list would also be revealed, we also need to explicitly hide that third level list (remember that we need to duplicate the :hover pseudo class with the .sfhover class):


#nav li:hover ul ul, #nav li.sfhover ul ul {
	left: -999em;
}

Now, this rule can be contradicted so that it is displayed when the corresponding list item is hovered over by expanding on the displaying of the dropdown (which with the single level dropdown was #nav li:hover ul, #nav li.sfhover ul { left: auto; }):


#nav li:hover ul, #nav li li:hover ul, #nav li.sfhover ul, #nav li li.sfhover ul {
	left: auto;
}

And that will establish a solid two level dropdown menu.

Following the same logic you can accommodate as many levels of dropdown menus as you want:

For three levels of dropdowns:


#nav li:hover ul ul, #nav li:hover ul ul ul, #nav li.sfhover ul ul, #nav li.sfhover ul ul ul {
	left: -999em;
}

#nav li:hover ul, #nav li li:hover ul, #nav li li li:hover ul, #nav li.sfhover ul, #nav li li.sfhover ul, #nav li li li.sfhover ul {
	left: auto;
}

And in the crazy event you need four levels:


#nav li:hover ul ul, #nav li:hover ul ul ul, #nav li:hover ul ul ul ul, #nav li.sfhover ul ul, #nav li.sfhover ul ul ul, #nav li.sfhover ul ul ul ul {
	left: -999em;
}

#nav li:hover ul, #nav li li:hover ul, #nav li li li:hover ul, #nav li li li li:hover ul, #nav li.sfhover ul, #nav li li.sfhover ul, #nav li li li.sfhover ul, #nav li li li li.sfhover ul {
	left: auto;
}

Examples

So you might have already looked at the one level, two level and three level bare-bones examples, which are probably the best places to take a look at the uncluttered source code in action, but, of course, you can make things look a bit prettier. You could even turn it into a vertical menu rather than a horizontal one.