A List Apart


Progressive Enhancement with CSS

Issue № 270

Progressive Enhancement with CSS

by Published in CSS, HTML, JavaScript70 Comments

In the previous article in this series, we covered the basic concept of progressive enhancement; now, we can begin discussing how to use it. There are many ways to integrate progressive enhancement into your work using Cascading Style Sheets (CSS), and this article will cover a few of the biggies and get you thinking about other ways to progressively enhance your sites.

Style sheet organization

A lot of web designers and developers don’t think much about how they incorporate stylesheets into their documents, but there is a real art to it. With the right methods, you can immediately gain many of the benefits of progressive enhancement.

Use multiple style sheets

There are many benefits to getting a little separation in your styles. Obviously, stylesheets that are 1500+ lines long are a bit hard to maintain and separating them into multiple stylesheets can improve your workflow (and your sanity). Another benefit not often considered is that this separation can help you obtain greater consistency of presentation across the media types you are targeting.

Consider taking your main.css file, which contains all of the style rules for your site, and breaking it up into individual stylesheets containing typographic, layout, and color information. Name the files accordingly: type.css, layout.css, color.css.

A graphic depicting the breakup of a single stylesheet into multiple, contextual stylesheets.

How a single stylesheet is divided into multiple, contextual stylesheets.

Once you’ve done that, you can use a little wizardry to automatically provide a “low-fi” experience to older browsers (such as Internet Explorer 5/Mac) and many other browsers lacking solid support for CSS layouts. How? Well, it’s all in how you link to the files. Let’s assume we started with a main.css file included via a link element:

<link rel="stylesheet" type="text/css" href="main.css" />

First, we’ll break it into separate calls to our three contextual stylesheets:

<link rel="stylesheet" type="text/css" href="type.css" />
<link rel="stylesheet" type="text/css" href="layout.css" />
<link rel="stylesheet" type="text/css" href="color.css" />

In the past, many of us used a media value of “screen,projection” to put the kibosh on Netscape 4.x getting the layout styles, but there’s a better way. Before we look at that solution though, let’s think about alternate media types.

Working with alternate media types

Since content delivery is the main focus of progressive enhancement and we want to deliver an “enhanced” experience to any device that will support it, we should really begin to think beyond the browser; most importantly, we should be thinking about print and mobile.

Unfortunately, the mobile market is still fragmented and immature (don’t get me started on all the handheld browsers that think they should render styles targeted at the “screen” media type). Consequently, a discussion of the ins and outs of handling that medium in a progressively-enhanced way would take several articles, if not a whole book. But don’t despair: things are beginning to gel a bit in the mobile world, and some very smart people are starting to put together resources to help you. But in the interest of time, and our collective sanity, we’ll focus on print.

Now, normally, we would add print styles with another link element:

<link rel="stylesheet" type="text/css" media="print" 
href="print.css" />

Traditionally, this stylesheet would contain all of our print-related rules, including typographic and color rules. In the case of typography in particular, the rules in our print stylesheet most likely mirror those in our main stylesheet. In other words, we have a lot of duplication.

This is where the separation of typographic and color styles from layout styles becomes a great asset: we no longer need those rules in our print stylesheet. On top of that, we can use another little organizational technique to improve the scalability of our site and hide certain layout styles from problematic browsers.

Let’s start by revisiting our stylesheets. Consider the following:

<link rel="stylesheet" type="text/css" href="type.css" />
<link rel="stylesheet" type="text/css" href="layout.css" />
<link rel="stylesheet" type="text/css" href="color.css" />

Now, since we don’t have a media type declared, Netscape 4.x will read any styles in these three files, but we can use its very basic understanding of CSS against it and provide further organization for our style rules by moving all of the styles that layout.css contained into a new stylesheet, appropriately named screen.css. Finally, we update the contents of layout.css to import screen.css and NS4.x and its ilk won’t be any the wiser (as they don’t understand the @import directive).

@import 'screen.css';

There’s still a bit of room for improvement though—we should really declare which media that this stylesheet is for, and we’ll do that by adding a media type to the @import declaration:

@import 'screen.css' screen;

The problem is that IE7 and below won’t understand this syntax and will ignore the stylesheet, but if you want to provide these styles to those browsers as well (which you probably will), you can do that quite easily using Conditional Comments, which we’ll cover below. Those of you with eagle eyes may also have noticed the use of single quotes (’) instead of a double quotes (”) around the stylesheet name. This is a nifty trick for getting IE5/Mac to ignore a stylesheet. And since IE5/Mac’s CSS layout is pretty sketchy (especially when it comes to floats and positioning), hiding layout rules is a perfectly acceptable way to treat it. After all, it will still get the color and typography information, which counts for something.

Using the same technique, we can import our print.css file (which contains, you guessed it, print-layout–specific rules).

@import 'screen.css' screen;
@import 'print.css' print;

Not only do we have nicely organized stylesheets now, we also have an effective means of progressively enhancing the design of our site.

Now for the $10M question: how do we deal with IE6?

To many folks, Internet Explorer 6 is the new Netscape 4—everyone just wishes it would go away.

We’ll skip the litany of problems in IE6; its issues are well-documented and, honestly, not all that difficult to work around. Furthermore, the adoption of IE7 has been especially swift (particularly in the consumer market) and IE8 is already in beta, meaning that one day, we may actually be able to bid adieu to the aging 6.

Whether it was intentional or not, Microsoft delivered a great tool for enabling progressive enhancement when it shipped IE5: conditional comments. These ingenious little bits of logic (which degrade to HTML comments in all other browsers) allow us not only to direct certain bits of markup at IE, but also to direct them at particular versions of IE.

As web standards-aware developers, we should always start by testing our designs in the most standards-compliant browsers available, and then provide fixes for those browsers that just need a little nudge to get them on the right track. Everyone’s workflow is different, but I have found it best to start every project with a standard set of files. My base set includes the following:

  • type.css
  • layout.css
  • screen.css
  • print.css
  • color.css

Then, depending on the requirements of the project, I add browser-specific CSS files that contain the “nudges.” In most current projects those are ie7.css and ie6.css, though if the project calls for supporting an IE version earlier than 6, I will create a corresponding file for that one as well (ie5.5.css, etc.). With those files in place, I begin adding my style rules to the applicable stylesheet in my base set.

I start all of my CSS testing in Mozilla Firefox because I write the bulk of my CSS using its Edit CSS sidebar. As soon as I have wrapped a page design in Firefox, I fire up other browsers to take a peek. Most perform flawlessly, as they grok web standards. Then it’s on to IE7. In most cases, I don’t find many issues, but occasionally there’s a need to invoke hasLayout or fix another minor layout glitch. Instead of adding those fixes to my base set of stylesheets, I add them to ie7.css and then link that file in the HEAD of the document, inside a conditional comment:

<!--[if lte IE 7]>
<link rel="stylesheet" type="text/css" href="ie7.css" />

That conditional comment directs that particular stylesheet to any version of IE less than or equal to (lte) 7. So, if someone comes to this page in IE7, they will get the fixes I have applied within, but if they come in a newer version—which may have fixed those rendering issues as IE8 has done by abolishing hasLayout—the stylesheet will be ignored. On the flip side, if someone comes to this page with IE6, they will get the fixes from this stylesheet, which is perfect, as any rendering error present in IE7 is likely to be present in IE6 as well. One such fix would be to cover for IE’s inability (in versions 7 and below) to understand a media-typed @import (as mentioned above) by adding an @import for screen.css to the top of ie7.css without declaring a media type, thereby importing the styles that were missed the first time around.

Once I am done adding the fixes for IE7, I open IE6 and see if it needs a little hand-holding as well. If it does, I add another conditional comment to the document, linking out to ie6.css.

<!--[if lte IE 7]>
<link rel="stylesheet" type="text/css" href="ie7.css" />

<link rel="stylesheet" type="text/css" href="ie6.css" />

Then, I simply add the fixes required for IE6 to that stylesheet and they will be ignored by IE7, but will still trickle down to IE5.5.5, etc.

Using conditional comments in this way allows you to easily manage the browsers you need to target for your project and keep your CSS files hack free.

Other considerations

Progressive enhancements in CSS aren’t limited to how we associate our stylesheets with our documents: we can also apply the concept to how we write our CSS.

Consider generated content, for instance. Not all browsers can handle it yet, but it is a great way to add in additional bits of design or text that are not necessary to the usability of the page, but that provide some visual or other enhancement.

For example, take this simple contact form:

A screenshot of the HTML form used in this example (code is provided below).

The HTML form used in this example (code is provided below).

When coding this, you may be tempted to put those colons (:) inside the label element. Why? Do they add anything to the labels? No. While they do serve a purpose, to provide additional visual cues to a user, they are superfluous to the label and should be left out (Line wraps marked » —Ed.):

<form id="contact-form" method="post">
    <legend>Contact Us
    <p>Send us a message. All fields are required.</p>
        <label for="contact-name">Name</label>
        <input type="text" id="contact-name" name="name" />
      </li>    <li>
        <label for="contact-email">Email</label>
        <input type="text" id="contact-email" name="email" />
    <li><label for="contact-message">Message</label>
        <textarea id="contact-message" name="message" rows="4" »
    <button type="submit">Send It</button>  </fieldset>

Generated content is a perfectly appropriate way to add them back into the document:

label:after {
  content: ":";

Approaching the form in this way gives us the flexibility to remove those decorations from our entire site by simply editing the CSS, rather than having to touch each and every form (and yes, I’ve been there). This technique also degrades nicely because the form is not rendered useless without the colons—a perfect example of progressive enhancement.

You may also find that writing rules with more advanced selectors helps you progressively enhance your site by targeting certain styles to more advanced browsers. One great example is the attribute selector, which is not understood (and is, therefore, ignored) by IE6 and others of its generation and earlier. Egor Kloos played with this concept beautifully in his submission to the CSS Zen Garden titled “Gemination.”

A comparison of Egor Kloos' CSS Zen Garden entry (“Gemination”) as viewed in standards aware browsers and IE6.

A comparison of Egor Kloos’ CSS Zen Garden entry (“Gemination”) as viewed in standards aware browsers and IE6.

How did he do it? Well, the following is a slightly modified sampling from his code:

/* <= IE 6 */
body {
  margin: 0;
  text-align: center;
  background: #600 none;
}/* IE 7, Mozilla, Safari, Opera */
body[id=css-zen-garden] {
  margin: 100px 0 0;
  padding: 0;
  text-align: center;
  background: transparent url(squidback.gif);

The differences are striking and illustrate beautifully how progressive enhancement can be implemented in your CSS.

In a similar vein, Andy Clarke had a little fun with IE6 on his site. By tapping into the filters available in IE and employing some conditional comments, he was able to strip all of the color from his website and provide some alternative imagery for a truly “low-fi” experience.

A comparison of Andy Clark's website in standards-aware browsers and IE6.

A comparison of Andy Clark’s website in standards-aware browsers and IE6.

The image-graying technique is accomplished by adding the following declaration block in a stylesheet directed at IE6 (and under) by using conditional comments:

/* =img for Internet Explorer < 6 */
img {
  filter: gray;

Though these two examples may include more tricks than you can get away with in your day-to-day work, they certainly serve as excellent proofs-of-concept for the ways in which you can practice progressive enhancement with CSS.

Putting it all together

As we’ve discussed, there are numerous ways to begin implementing progressive enhancement on your site using CSS. The easiest, and perhaps best, way to begin is by organizing your stylesheets and making informed decisions about how those stylesheets will be connected to your document. Handling the idiosyncrasies of IE is a breeze once you understand the power of conditional comments, and you can accomplish more granular tweaking in the CSS itself if you’re discerning about the selectors you choose and the situations in which you use them.

Armed with this knowledge, you should be well on your way to becoming a progressive enhancement expert. Join me next time, when we discuss progressive enhancement with JavaScript.

70 Reader Comments

Load Comments