Son of a Son of Suckerfish!

Posted on Mar 20, 2008 in CSS | 22 comments


I’ve been using the lovely Son of Suckerfish method to make drop down menus for years now, and it is a very robust way to make them. The one criticism (the word criticism is an overstatement really…) I have is that for every sub-menu level, you need to add to the CSS in order for that to show. Now, it’s not a really big inconvenience… how many sub-levels do you need anyway… but wouldn’t it be nice if we didn’t have to do that? I have made a very small change to the CSS and Javascript that takes care of this problem. Here is the edited CSS:


body {
	font-family: arial, helvetica, serif;
}

#nav, #nav ul { /* all lists */
	padding: 0;
	margin: 0;
	list-style: none;
	line-height: 1.2em;
}#nav a {
	display: block;
	width: 10em;
}

#nav li { /* all list items */
	float: left;
	width: 10em; /* width needed or else Opera goes nuts */
}#nav li ul { /* second-level lists */
	position: absolute;
	background: orange;
	width: 10em;
	left: -999em; /* using left instead of display to hide menus
			because display: none isn't read by screen readers */
}

#nav li ul ul { /* third-and-above-level lists */
	margin: -1.2em 0 0 10em;
}/*Show the submenu DIRECTLY INSIDE the selecte LI tag*/
#nav li:hover > ul, #nav .soasfhover{
	left: auto;
}
/*Fix for IE7*/
#nav li:hover {
	position: static;
}

So there three difference between this and the original (I am assuming here that you have read Son of Suckerfish, as there’s no point re-explaining a perfectly good tutorial). First difference is I got rid of this rule :

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

Second difference is I changed this:

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

To this:

/*Show the submenu DIRECTLY INSIDE the select LI tag*/
#nav li:hover > ul, #nav .soasfhover {
	left: auto;
}

What I did here was confine the “left:auto” property to only be applied to the UL tag one level below the LI tag being hovered on by using the CSS Child selector “>”. The second selector is for those annoying browsers that aren’t up to speed with CSS2 (i.e. < IE7). The javascript that leverages this selector will be explained further down. This means that no other UL tags below the hovered LI tag are displayed, and therefore no rule is needed to hide them. The beauty of this is you don’t need to worry about how many levels you will have in your drop down menu, as this covers as many (or little) levels you may require. With regards to browser compatability, this works with most recent versions of all popular browsers (inc. IE7). However, lower than IE7 doesn’t support the > CSS selector, and so the javascript provides the fail safe. I have also slightly modified the Javascript used in the original Son of Suckerfish :

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

What this does different is select all the <ul> tags in the Drop Down Menu (which are the sub-menus) and assign their containing <li> parent the mouse events, which then add/remove the soasfhover class mentioned earlier in the CSS script. Another difference between this and the old script is that it only adds events to <li> elements that contain a sub-menu, and not every <li> in the menu. The third difference solves a problem in IE7, which is taken from builtfromsource.com:

#nav li:hover {
	position: static;
}

You can see a working example here, and (updated 23/03/2008) also a new version using MooTools JS in place of the code used above. Here’s also a vertical menu version.