A List Apart

Menu
Issue № 161

Sliding Doors of CSS, Part II

by Published in CSS, HTML, Interaction Design · 101 Comments

A note from the editors: While brilliant for its time, this article no longer reflects modern best practices.

Sliding Doors of CSS (Part I) introduced a new technique for creating visually stunning interface elements with simple, text-based, semantic markup. In Part II, we’ll push the technique even further. If you haven’t read Part I yet, you should read it now.

Article Continues Below

Here, we’ll cover a new scenario where no tab is highlighted, combine Sliding Doors with a single-image rollover, provide a fix for the clickable region in IE/Win, and suggest an alternate method of targeting tabs. We’ll skip a basic recap of the technique (see Part I for this) in favor of jumping right back in where we left off.

No Current Tab

In Part I, we didn’t account for cases where there might not be a “current” tab that gets highlighted. For example, a registration process, or pages containing legal text, might not fit under any of the sections represented by tabs. If none of the tabs are styled as the “current” tab, the rule which adds an additional 1 pixel of bottom padding won’t get used. Thus, the tabs end up obscuring the rule running along the tabs’ bottom edge.

Adding a bottom border of 1px to all “non-current” tabs, then removing the bottom border in case of an existing “current” tab, provides an easy fix:

#header li {
  float:left;
  background:url("left.gif")
    no-repeat left top;
  margin:0;
  padding:0 0 0 9px;
  border-bottom:1px solid #765;
  }
#header #current {
  background-image:url("left_on.gif");
  border-width:0;
  }

When we leave out a current tab, the effects of our style changes can be seen in Example 7.

Single-image Rollovers

For the sake of simplicity, we intentionally left out the subject of rollovers in Part I. Now that we have the basic technique under control, we can start combining it with others to expand the utility and behavior.

Until recently, implementing any kind of rollover effect — whether with JavaScript or with CSS — meant creating two sets of images: one for the normal state, another for the hover state. To avoid a delay caused by separately downloading the hover-state image, numerous methods exist for “preloading” the required images into the browser cache. Petr Stanicek (aka “Pixy”) shows us in “Fast Rollovers, No Preload Needed” how to combine both states (normal and hover) into a single image, eliminating the need for preloading.

For our example, we stack our two left images on top of each other to combine the normal and hover states into one new image. We do the same for the right image. What were once 150-pixel tall images are now 300 pixels tall. We end up with left_both.gif and right_both.gif. With these new images, we can take advantage of the CSS background-position property to shift into view the appropriate portion of the combined-state background image when the user hovers over a tab:

[When the combined-state background image is positioned at the top of the doorway, the normal state is visible. When we shift the background image up by a certain amount, the hover state is visible.]

We switch to these new images for the list item and anchor, keeping the same position:

#header li {
  float:left;
  background:url("left_both.gif")
    no-repeat left top;
  margin:0;
  padding:0 0 0 9px;
  border-bottom:1px solid #765;
  }
#header a {
  float:left;
  display:block;
  background:url("right_both.gif")
    no-repeat right top;
  padding:5px 15px 4px 6px;
  text-decoration:none;
  font-weight:bold;
  color:#765;
  }

When dealing with the background-position property, we must specify two values,  horizontal and vertical, and they must be specified in that order. We can’t combine keywords (left, right, top, etc.) like we’ve been using with length or percentage values. So when specifying positions for the hover state, we avoid using keywords altogether. We use 0% for the left image to position its left edge against the left side of the doorway, and 100% for the right image to do the opposite.

Since we know exactly how far down in the new image each different state begins, we can vertically position the background images by a precise pixel amount. The top 150 pixels of these images holds the normal state, the bottom 150 pixels holds the hover state. So for both left and right images, we simply push the background images up by using a negative value of 150px. We also double up on the selectors for the first rule so we only need to specify the text color once:

#header li:hover, #header li:hover a {
  background-position:0% -150px;
  color:#333;
  }
#header li:hover a {
  background-position:100% -150px;
  }

We can use the same combined-state images for the current tab. Instead of specifying a new image as we were previously doing, we use the same shifted background positions from the hover states:

#header #current {
  background-position:0% -150px;
  border-width:0;
  }
#header #current a {
  background-position:100% -150px;
  padding-bottom:5px;
  color:#333;
  }

Implementing rollovers is as simple as that. See them in action in Example 8. We’ve dropped the total number of images we’re using from five (2 left, 2 right, and 1 background behind the tabs) down to three (1 left, 1 right, 1 background), and eliminated the need to do any image preloading.

If you’ve been checking our work so far in Internet Explorer (Win or Mac), no doubt you’ve noticed that the rollover effects, as implemented above, don’t work. IE only applies the :hover pseudo class to anchor elements, nothing else. In order to change both images of the Sliding Doors technique, we would need to insert an additional element (such as a span) inside the anchor, and shift all our style rules one element inward (list item rules shift to the anchor, anchor rules shift to the span).

We won’t review in detail the adjustments required to get both images changing for a rollover effect in IE. But to prove it is possible, we can see these changes demonstrated in Example 8a. As you can see, shifting the roles of each element also eliminates the small amount of dead space we mentioned in Part I, because the anchor now contains the entire tab.

Rollovers are often (more-or-less) a decorative effect. Some of you may decide the extra markup needed isn’t worth the advantage of getting rollovers to work in Internet Explorer. Others may decide the extra spans are a small sacrifice to have rollovers working in all popular browsers and to eliminate the dead space in previous examples. Whether or not to insert the extra markup is up to you.

Clickable Region Fix

As in the case of our tab examples from Part I, navigation links can be turned into block-level elements and given extra padding to increase the clickable region of the link. The visual region is most often filled with a background color (or background image in our case) which implies that the user can click anywhere within this region, not just on the contents of the link. In most browsers, when an anchor element is changed to a block-level element (via CSS) and additional padding is applied to this anchor, its visual and clickable regions expand together to cover the contents and the padding of the link. Unfortunately, IE/Win will only expand the visual region, confining the clickable region to the anchor’s contents, not inclusive of its padding:

[The clickable region in most browsers expands to the entire visible area of the tab, but Internet Explorer for Windows will only make the text clickable.]

In Part I (and just after Example 8a above), we briefly mentioned a small amount of dead space on the left side of the tab caused by the transition to transparent-corner images. We also noted the requirement to avoid this dead space. However, Part I avoids covering the limited “clickable region” issue in IE/Win. This browser (version 6.0 and lower) suffers from several bugs in its implementation of CSS. One of the bugs produces unintended — and sometimes unrecognized — problems in the usability and accessibility of CSS-styled navigation.

Specifying either width or height for the anchor will magically force IE to expand the clickable region as well. But doing so would inhibit the flexible size of our doorway in other browsers. For our tab example, you might think we could use the “ems” unit to specify a width or height. This would size our tabs based on the already-inherited font size of the text inside. But specifying a height for the anchor makes IE/Win go bonkers. And unless we’re using a monospaced font for our tab text, specifying a width in ems will make the tab width inconsistent with the text inside as it gets resized. (Not to mention the pain of determining an appropriate width for each piece of text, then needing to re-specify the width every time the tab text gets changed.)

Fortunately for us, we can exploit a different flaw in IE/Win’s implementation of CSS, forcing the expansion of the clickable region in this browser, without needing to guess at an arbitrary width. All we need to do is specify a small width for the anchor. Most browsers will pay attention to — and honor — the width property for a block-level element, even if the contents inside the element don’t fit within that width. The element will shrink to the specified width, even if it makes the text inside poke beyond the element’s boundaries. But IE/Win will only shrink the element to the width of the longest non-wrapping line of text.

So even if we specify a tiny width for the anchor (like .1em), IE/Win will still allow the anchor to be as wide as the text inside. At the same time, IE will also expand the clickable region to fill the entire tab:

#header a {
  float:left;
  display:block;
  width:.1em;
  background:url("right.gif")
    no-repeat right top;
  padding:5px 15px 4px 6px;
  text-decoration:none;
  font-weight:bold;
  color:#765;
}

This makes no sense whatsoever, as the two concepts work in direct opposition to each other. But it works, and fixes the clickable region for IE/Win. We need to keep in mind that other browsers honor this width specification, and will actually attempt to shrink the width of each tab down to .1em + padding. Happily, IE/Win (6.0 and lower) also doesn’t understand the CSS child selector — so we can use one to reset the width of the anchor back to “auto” for all other browsers, allowing the tabs to expand and contract as normal:

#header > ul a {width:auto;}

Example 9 will fix the clickable region problem in IE/Win, and its sad little IE-centric hacks should be invisible to all other browsers.

Targeting Tabs

All examples in Part I used an ID applied to a single list item to alter the appearance of the “current” tab. The result of moving the ID from one list item to another is an easy concept to understand for someone new to CSS. But an alternate means of targeting the current tab may be more efficient in many cases, even though it adds a small amount of markup.

Instead of using a single id=“current” to identify the current tab in the markup, we can apply unique IDs to each list item, like so:

<div id="header">
  <ul>
    <li id="nav-home"><a href="#">Home</a>
  </li>
    <li id="nav-news"><a href="#">News</a>
  </li>
    <li id="nav-products"><a href="#">Products</a>
  </li>
    <li id="nav-about"><a href="#">About</a>
  </li>
    <li id="nav-contact"><a href="#">Contact</a>
  </li>
  </ul>
</div>

We also apply an ID to a larger containing element (like the body). The ID value corresponds with a section into which this page fits. This body ID can also be used to add unique section-specific styles to other portions of the page. With IDs in both places, we can alter the appearance of a certain tab if it meets the conditions of descendant selectors. Rather than use #current as part of our selector, we’ll use combinations of body and list item IDs to set the conditions for when a tab is considered “current”:

<strong>#home #nav-home, #news #nav-news,
#products #nav-products, 
#about #nav-about,
#contact #nav-contact {
  background-position:0% -150px;
  border-width:0;
  }
#home #nav-home a, 
#news #nav-news a,
#products #nav-products a, 
#about #nav-about a,
#contact #nav-contact a {
  background-position:100% -150px;
  color:#333;
  padding-bottom:5px;
  }

Example 10 displays the effects of applying id=“news” to the body, and Example 10a shows what happens when the body uses id=“products”. {A List Apart’s navigation uses body id the same way. – Ed.}

Additional Notes

Box-tops: You may have expandable modules on your pages which draw a box around a header and its supporting content. Assuming that you’re using a wrapper (like a div) which contains the module’s header and content, you already have the two elements for each background image (the div and header). In this case, you’ll most likely want to place the narrow image on the right, as shown in Example 2. This will give you complete control over the left starting point of the heading text. Fade the bottom of each image into the background color of the containing box so they appear to blend together as one unit.

Turning Sideways: If you can roughly predict the height of an interface element, (or if you create an image large enough to accommodate vertical expansion) you can turn the “doorway” on its side, using one image for the top and one for the bottom (instead of left and right). Remember to take into account extreme text wrapping which might occur with narrow browser widths or enlarged text sizes.

IE Flicker: If you’re seeing a flicker of the images when hovering over the tabs in IE/Win, check the cache settings for temporary files (Tools > Internet Options > General tab > Settings button). You may have changed the setting from the default to make sure you’re seeing the newest version on every page refresh. IE/Win has trouble holding a background image steady on anchors if you’ve specified “Every visit to the page” for temporary files. The default setting is “Automatically”, which allows the browser to instantly retrieve the image from cache, preventing any flicker. Most users never change this setting; most likely, they don’t even know it exists.

Multi-word Tabs: As may often be the case, if you need to use text for a tab that consists of more than a single word, you’ll most likely want to add a white-space:nowrap; declaration to the anchor rule, preventing the tab text from wrapping in certain browsers.

There might be other issues, alterations, and variations on this technique which already exist, or crop up over time. But we’ll stop here for now. Hopefully we’ve filled in gaps and resolved a few uncertainties surrounding the usefulness and extensibility of Sliding Doors. Onward and upward.

About the Author

101 Reader Comments

Load Comments