DHTML menu using semantic markup

I’m so hooked on CSS-based pages, that it hurts. It’s such a great thing to see how one small feature can make wonders. I.e. CSS float property allows extremely flexible layouts - page will accomodate to the device you are using.

Another CSS miracle is the display:inline + list-style-type:none combo. Simple, understandable UL list can be tweaked to produce magnificent things. There’s a great deal of fantastic examples around the web: bread crumbs, vertical and horizontal menus…with one small things that shortly comes to mind - these are all rather simple examples.

I needed a replacement for the old web-developer favourite - multi-level menu. Over the years, it was created in various ways, using tables, divs, NN4 layers, with inexhaustible charade of browser tweaks.

These were my goals:

Ideal solution would be to avoid Javascript at all. That is possible… if you have the privilege to ignore 99% of Internet surfers. Internet Explorer does not support those. …(blues moment)… So, let’s dive into real world, as it exists today.


I started-out with the Dave Lindquist’ great example at, which has ground-up already built. I played few days with it, and then started adding features like automatic menu generation, multiple levels, disabled items, icons…

After several days, I came up with nearly semantic markup. I had to add spans to support two icons (one for item, another for sub menu arrow). I’m still not satisfied, and I’m playing with dynamic elements generation to avoid using spans all together. Next version will come up shortly, as time permits.

For position calculations, I used Mike Foster’ fantastic X lib and simplified the functions (since I don’t need 4.0 browsers support). It was all working very good, with not a single browser-tweak. To show-off the possibilities, I emulated the WinXP menu style, and though added DropShadow using filter property. This cost me few hours of frustrating debugging, trying to figure out why the hell were nested elements cut in IE. I found (hard way) that IE cuts off the available area when filter is applied. So I ditched it.

All was good. And then it came…


The dreaded windowed-controls problem. You just can’t draw over select list, for example. At first, I went with old work around - show/hide such elements when menu is used. But that simply su*ks. Yes, it really does. But there was nothing that could be done.

At the same time, I was working with several ASP.NET components that were outputing DHTML menus. They all had horrible monster tables output with several scripts for each browser, but they could also draw over select lists.


I started to search frantically and finally came around Joe King’ page where he explains a great trick - place one iFrame just below the menu, and it will hide the wind. controls; and iFrame can be drawn-over in IE 5.5+. This worked in Mozilla too, and also in…To my great surprise, I found out that Opera 7 already has this trick built-in, so I turned that feature off for Opera.

iFrame is dynamically generated using IE’s insertAdjacentHTML method. I used Thor Larholm’ code to prototype this method for DOM browsers. Life was a bliss.

There was a slight bit of iFrame visible below the menu (it depends on the .css used for menu) so I used the filter property on iFrame to make it transparent. More tests showed that this kills IE5.5, even with SP2 installed. That introduced more browser-based tweaking. Since I was in the shop, I added more tweaking to support IE5, which uses show/hide work around (IE5’ iFrames are also windowed controls).


HTML definition looks like this:

    <ul id="menuList">
      <a href="#" id="mmi" class="actuator">Main menu item</a>
      <ul class="menu" id="mmiMenu">
        <a href="#" id="item" class="actuator">
        <span class="itemarrow"></span>
        <span class="itemtext">Sub item</span>

id="menuList" defines the menu. Each menu item that have submenus must have class="actuator", since that is used as submenu identifier in the processing function, called on window.onload (ProcessItems). Actuator’s item ID is important - it’s a prefix in the ID of the nested UL list. I.e. item id="mmi" will have a submenu with id="mmiMenu". Be carefull not to mistype - DOM is case-sensitive.

Item text is placed inside itemtext span, while the empty itemarrow span is there as a place holder for submenu arrow. I know. Working on it.

Item icons are added using CSS, which is explained in the following part.

…and CSS

Script is heavily dependent on the CSS used. Bad style can even break the scripting engine, so be alert - if you encounter problems, first check have you followed the following rules.

First, main UL (id="menuList") must have position:relative. This defines new coordinate system for the submenus, which have position:absolute, and thus allows simple re-positioning using xLeft, xWidth etc.

Submenu items A elements must have display:block, so that width, height and rest of the block properties are applied. That’s not mandatory for main menu items, but that is up to you.

itemtext span should be also converted to block element if you have submenus and/or want to use icons. For item icons, add padding-left using slightly larger value then the width of your icons. If you have 2nd level submenus, then add padding-right to leave some space for the arrow. Actual value depends on the size of the arrow.

Rest of the parameters are up to you. CSS offers great deal of styling options; excellent collection of various horizontal and vertical lists can be found at Listamatic. All of those can be used for this menu.

For icons and submenu arrows, use one of the many background-image replacement techniques. For the basic tutorial on how this is done check Doug’s tutorial, and for an overview of improvements and current tricks see Dave’s mezzoblue.

Over and out

And there it is, version 1.0. Feel free to use this code. Play with it. Report problems and solutions. It’s bound to have problems now. It needs tests on Mac, Linux… Those SPANs pierce my eyes… But it’s a start.

Download link below will always lead to latest version of the script. Look here for future updates, as well as setup instructions and examples for the lastest version.

Have fun.