CSS, Modernizr, and progressive enhancement

I’ve finally settled on the best way to use Modernizr’s CSS classes.

Short story: definitely go the progressive enhancement route.

Longer story: if you’re not familiar with Modernizr, here’s how it works:

In your <html> tag, you add a class of no-js (think “no JavaScript”):

<html class="no-js">

Then you include the Modernizr script, which detects whether or not the browser supports various CSS, HTML5, and script features. Based on the results of those tests, Modernizr adds more classes to the HTML element. For example, if the browser supports CSS Animations, it will do this to the <html> tag:

<html class="js cssanimation">

And if the browser doesn’t support CSS animations, you’ll get this:

<html class="js no-cssanimation">

(Surely you’ve noticed that Modernizr also swaps out the no-js class for js. Because if Modernizr is running, you have JavaScript.)

With those classes on the very highest element, you can then use them in your CSS to target browsers that support or do not support certain features:

.svg {
  /* The browser supports SVG! Do something vector-y */
}

.no-svg {
  /* No SVG support here, so do something pixel-y */
}

Modernizr lets you test for like a zillion features and has some other niceties, but I just want to talk about the CSS classes right now.

Broadly, there are two ways to approach using these classes: for progressive enhancement or for graceful degradation.

With the progressive enhancement approach, you take the stance that your baseline styles should support everyone at a minimal level. Then, you can pile on fancy things only for environments that support them. Using Modernizr classes, that’d look like this:

.boogie {
  /* We define a basic dance party and everyone is invited! */
}

.cssanimations .boogie {
  /* Code for for fancy browsers that can do the foxtrot */
}

(Ignore my class names. They are nonsense. I’m just making a point.)

Using the other approach, graceful degradation (or maybe regressive enhancement, you work the other way. You write your baseline styles for the good browsers but provide backups for the others that aren’t so fancy:

.boogie {
  /* Code that assumes everyone can do the foxtrot */
}

.no-cssanimations .boogie {
  /* An alternative for browsers whose mom 
     made them go to the dance party */
}

At first glance, the graceful degradation route sounds the most appealing. After all, most browsers are pretty decent these days. Also, most people don’t disable JavaScript. And it seems easier to assume that the browser will be competent enough and then only patch it up when it isn’t.

It’s also tempting to go this route because many CSS declarations will be ignored if the browser can’t understand them anyway. Take transitions:

.fade {
  transition: opacity 1s ease;
}

.no-csstransitions .fade {
  /* 
   * Uh, you really don't need anything. 
   * Browsers that can't do transitions ignore the above 'transition' property 
   */
}

Or enhancements like multiple backgrounds, where you can use the cascade to your advantage and skip Modernizr classes completely:

.many-backgrounded-thing {
  /* This will work everywhere */
  background: url('an_image.png');

  /* 
   * If the browser supports multiple background syntax, this will override
   * If the browser doesn't, it will just ignore it
   */
  background: url('first_image.png'), url('second_image.png');
}

Clever, right? I like clever.

However, in my experience, you end up writing a whole lot more (and subjectively worse) code overall with graceful degradation than you would with progressive enhancement. Here’s a simplified example that I think should illustrate why.

Let’s say you’re making something that will eventually rely on JavaScript, like tabs. You use a pretty standard (simplified here) list markup:

<ul class="tabs">
  <li class="active"><a href="#panel1">Panel 1</a></li>
  <li><a href="#panel2">Panel 2</a></li>
  <li><a href="#panel3">Panel 3</a></li>
</ul>

<div class="tab-content active" id="panel1">Some content</div>
<div class="tab-content" id="panel2">Some content</div>
<div class="tab-content" id="panel3">Some content</div>

And here’s your CSS to display the links in a row and make ’em look like tabs. This is taking the graceful degradation approach, where we’re assuming the user has JavaScript (a totally reasonable assumption!):

.tabs li {
  display: inline-block;
}

.tabs a {
  display: block;
  /* Code for colors, borders, alignment, spacing, probably other things */
}

.tab-content {
  display: none;
}

.tab-content.active {
  display: block;
}

Simple enough. But what if the user has disabled JavaScript? Tabs won’t work! But we have the .no-js class available to un-do the tabbed styling and otherwise patch it up:

/* 
 … same tabbed code as above, plus this:
*/

.no-js .tabs li {
    display: list-item;
}

.no-js .tabs a {
    display: inline;
    margin: 0;
    padding: 0;
    border: none;
    /* More code to un-do all your pretty colors and make it look like a boring link again */
}

.no-js .tab-content {
    display: block;
}

See what happened there? In this case (and I’ve found that it is often the case), patch it up actually means un-do almost everything. And that’s a lot of specificity and cascading to deal with. And did you remember that the default display for <li> is list-item and not block?

With all the setting and then un-setting, your code smells; you write more CSS to do less stuff. Not so clever now.

And with Modernizr, you can get into double trouble. Because if your visitor has disabled JavaScript, you won’t have Modernizr and you won’t get the .no-cssanimations feature detection class, so you must add a .no-js declaration with every .no-whatever declaration:

.boogie { /* Dance party for everyone! */ }

.no-js .boogie,
.no-cssanimations .boogie {
  /* An alternative for browsers whose mom 
     made them go to the dance party */
}

On the other hand, here are our tabs with progressive enhancement:

.no-js .tabs li { 
  /* Absolutely nothing. They're fine by default. 
     In fact, delete this whole thing. */ 
}

.js .tabs li {
  display: inline-block;
}

.js .tabs a {
  display: block;
  /* Code for colors, borders, alignment, spacing, probably other things */
}

.js .tab-content {
  display: none;
}

.js .tab-content.active {
  display: block;
}

Yeah, you’ve gotta stick .js or .cssanimations or whatever in front of all your fancy code, but in the end you write less – typically much less – code.

And using the positive classes instead of cascade tricks is also – to me, at least – clearer what’s going on at a glance, even when it’s technically unnecessary:

.many-backgrounded-thing {
  background: url('image.png');
}

.multiplebgs .many-backgrounded-thing {
  background: url('first_image.png'), url('second_image.png');
}

So I say go this route. Use the positive (.js or .cssanimations) classes instead of the negative ones (.no-js or .no-cssanimations). Even if you think you can work around them, you’ll thank yourself later.

Am I doing it wrong?

Comments? I don’t do open comments. Life is too short.

If you have something to say, get in touch via .(JavaScript must be enabled to view this email address) or on Twitter.