September 30, 2003 · Dan Cederholm

Accessible Image-Tab Rollovers

I recently implemented a new navigation system for Fast Company and thought it'd be useful to document the process.

The Problem

We needed to fit more items into FC's top navigation. We ran out of room. Previously, this was handled by a simple, styled unordered list. But at a window resolution of 800x600 there wasn't enough additional horizontal space to add even one more item using the current design.

The Solution

I choose to combine and modify Pixy's brilliant Fast Rollovers and Stuart Langridge's accessible image replacement technique to create accessible, Javascript free, image-tab rollovers.

How does it work?

The XHTML: One List to Rule Them All

I wanted to continue to use a simple unordered list for the navigation in the markup. Much has already been said about using lists for navigation, here and elsewhere. They're compact, lightweight and accessible to text browsers, screenreaders, PDAs, phones, etc.

Here's what the list looked like originally (I've deleted some of the actual items to make it more convenient to demonstrate):

<ul id="nav">
<li><a href="/" class="selected">Home</a></li>
<li><a href="/guides/">Guides</a></li>
<li><a href="/magazine/">Magazine</a></li>
<li><a href="/articles/">Archives</a></li>
</ul>

Nice and simple. Now let's add a unique id to each li so that we can do some fancy stuff with it (namely, replace the boring text with stylized tabs):

<ul id="nav">
<li id="thome"><a href="/" class="selected">Home</a></li>
<li id="tguides"><a href="/guides/">Guides</a></li>
<li id="tmag"><a href="/magazine/">Magazine</a></li>
<li id="tarchives"><a href="/articles/">Archives</a></li>
</ul>

Now we're ready to create some tab images.

One Image, 3 States

The essence of Pixy's Fast Rollovers involves creating one image for each navigation item that includes normal, hover and active states stacked on top of each other. Later, we'll use CSS to change the background-position to reveal each state at the appropriate time.

fig. 1
Figure 1.1

Figure 1.1 on the right shows an example image that I've created and used for Fast Company's new navigation. Each state is 20px tall with a total image height of 60px. The top 20px is the normal state, the next 20px shows the hover state and final 20px shows the active state (which is also used for the "you are here" effect). There are similar images for each tab we'd like to use.

Using one image for each state allows us to toss out ugly Javascript and instead make use of simple CSS rules for hover effects. This is good. It also eliminates the "flicker" effect that other CSS methods suffer from, where separate on/off images are used. This is good. We also don't have to pre-load any additional images. Again... this is good.

The CSS: This is Where the Magic Happens

First we'll set up the rules that all navigation items will need. This will save us from writing duplicate rules for each tab. Then we'll add a separate rule for each list item id, giving the li it's own background-image and width -- the only two variables that will be different for each tab.

The CSS goes something like this:

#nav {
margin: 0;
padding: 0;
height: 20px;
list-style: none;
display: inline;
overflow: hidden;
}
#nav li {
margin: 0;
padding: 0;
list-style: none;
display: inline;
}
#nav a {
float: left;
padding: 20px 0 0 0;
overflow: hidden;
height: 0px !important;
height /**/:20px; /* for IE5/Win only */
}
#nav a:hover {
background-position: 0 -20px;
}
#nav a:active, #nav a.selected {
background-position: 0 -40px;
}

This essentially turns off padding and list styles, makes the list horizontal and hides the text that's between each hyperlink in the list. Notice the :hover and :active rules. These are generic for every a element within #nav so that we don't have to repeat those particular rules for each item.

I've also assigned a "selected" class to a tab that I wish to highlight permanently, signifying which section of the site you are currently on. This is shared with the :active state.

You may also notice that list-style: none; and display: inline; are repeated in both the #nav and #nav li selectors. This was to keep IE5/Win happy. In a perfect world, declaring this once for #nav would be perfectly sufficient. That's not the case, of course.

Next, we'll add the rule for each id and assign it's background-image and width. Here's an example:

#thome a  {
width: 40px;
background: url(home.gif) top left no-repeat;
}

There's a similar declaration for each tab needed.

The Results

fig. 2
Figure 1.2

Figure 1.2 shows the resulting tabs in normal, hover and selected state. To see it all working in action, check out the working example with sourcecode, or better yet, the real-world implementation at fastcompany.com.

Why use it?

  • It's lightweight: Just an unordered list in the markup.
  • It's accessible: Using Stuart's method, we can insure screenreaders will read the text links.
  • No Javascript: We don't need to pre-load or create multiple images for each state. We also don't need extra Javascript to control hover effects. Thanks, Pixy.
  • It's stylized: Fitting hypertext into defined areas can be tricky, this allows for using stylized images.

But Wait, the Text Doesn't Scale!

Following a great suggestion from Doug Bowman, and in response to legibility issues and the inability to resize image text, I went a step further and created second set of tab images with larger text labels. I could then override rules on the exisiting "medium" and "large" alternate stylesheets. The alternate styles are activated using Paul Sowden's Stylesheet switcher.

An example of the overriden rule looks almost identical to the original, with a new width and image path:

#thome a  {
width: 46px;
background: url(guides_lg.gif) top left no-repeat;
}

fig. 3
Figure 1.3

Figure 1.3 shows the larger tabs as they appear on Fast Company, where you'll notice that the horizontal spacing is tighter while the vertical size remains the same as the original. But, by adding the ability to increase the size of hypertext as well as the tab images we've helped out low vision users, while still working with our certain design constraints. Thanks to Doug for this solution.

Compatibility

Tested and working in Windows: Mozilla, Netscape7, IE5.0+; Mac: Mozilla, Camino, IE5, Safari.

Specifically for Fast Company, I choose to position: absolute the #nav in order to make things line up perfectly, letting the background color of the header area show through underneath. This works fine and dandy -- except in Opera7/Win where specifying a width is necessary for absolutely positioned elements (ugh). That's OK though, we'll just add the total width of the images (added up) to the #nav:

#nav {
margin: 0;
padding: 0;
height: 20px;
list-style: none;
display: inline;
overflow: hidden;
width: 201px;
}

Now we can sleep at night, and Opera fans rejoice.

Special thanks to Paul Maiorana for letting me bounce ideas off of him (repeatedly) at the office.