Photo of Nathan Shubert-Harbison Nathan Shubert-Harbison

Main content

Progressive enhancement & graceful degradation with Modernizr

So many different devices are used to access the web—from the top of the line, full-feature smartphone to the decade-old desktop running a legacy browser.

The former totes support for everything new and shiny and a touchscreen to boot, while the latter is limited in features. The two examples are disparate, but we need to develop websites for this very spectrum. Modernizr helps us do that.

What is Modernizr?

Modernizr is a JavaScript library designed to help create cross-platform, cross-device websites and web apps. It does this by testing the browser the user is on, and reporting back on what the browser is and isn’t capable of. As a developer, you can use this information to adapt your website accordingly.

Advantages of feature detection

Unlike some other approaches, Modernizr doesn’t check what your visitor’s browser or device is. Modernizr checks what the browser and device can do. For example, Modernizr won’t tell you if your visitor is using Internet Explorer 8 on a desktop computer. It will however tell you that your visitor is using a browser that doesn’t support CSS3 rounded corners and touch interaction, for example. This philosophy of detecting capabilities rather than devices has a few key advantages.

Unreliability of the user-agent string

When you visit a website, your browser gives that site some information about itself through something called the user-agent string. Here’s an example of a user-agent string from Internet Explorer 7:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)

The user-agent string can be used to target specific functionality to specific browsers. You could use this approach to do something specific on an iPad, for example.

The main problem with this approach is that you need an exhaustive list of user-agent strings that is constantly being maintained. If iOS gets updated with a new version of mobile Safari and the user-agent string changes, for example, you’re going to need to update your list.

You can also get unreliable results from the user-agent string. For example, when Google Chrome first came out for iOS, many mobile websites broke in that browser. Although visitors were still coming from an iPhone, websites didn’t recognize the Google Chrome user-agent string so they weren’t mobile-optimized (at best), or the were broken (at worst).

Unexpected device support

Where targeting the user-agent string requires a comprehensive list of strings, feature detection lets you optimize your sites for devices and browsers that might not be on the list but have recognizable features. You could be providing support for browsers and devices that you’ve never even heard of. There are many Android tablets out there these days, for example. If you target your tablet features to devices that have tablet-like capabilities, you’ll find yourself having optimized your site for a vast range of devices.

Future friendly

Similar to supporting unexpected devices, Modernizr allows you to to support new devices after your site launches. Whereas a user-agent string-based solution would require updating the list of strings when a new device comes out, a site using Modernizr will be optimized without any additional effort.

Internet Explorer specific CSS

A common technique for Internet Explorer support is to load CSS specifically for Internet Explorer, and apply classes specific to the version number. Sometimes you have to do this, but Modernizr offers better solutions in many cases. For example, Internet Explorer doesn’t support CSS3, but if you use Modernizr to provide fallbacks, you support all scenarios where CSS3 support might not be available, not just Internet Explorer.

Progressive enhancement & graceful degradation

There are two broad categories of approaches that can be used to support a wide spectrum of devices and browsers: progressive enhancement and graceful degradation.

With progressive enhancement we start with a nice baseline and enhance it based on what the browser and device can support.

Graceful degradation, on the other hand, starts with a feature-rich product and provides fallbacks in situations where some more advanced features aren’t supported. Modernizr helps us take both approaches, as we’ll see.

How to use Modernizr

The first step in using Modernizr is going to moderizr.com and downloading the script.

There are two versions of Modernizr: a fully packed development version, and a customizable production version. I’ll come back to the customizable option, but for now, grab a copy of the development version and include it in your document before your site’s scripts.

Once you have Modernizr included, you’re ready to go. Just by including it, Modernizr is testing your browser and device, and reporting back its findings. Now it’s time to use that information, and we can do this with both CSS and JavaScript.

Using Modernizr in CSS

After it performs tests, Modernizr reports information back to you by adding classes to the html element. Your html element will look like this, depending on which tests ran:

<html class=“js canvas canvastext no-geolocation rgba hsla multiplebgs borderimage
borderradius boxshadow opacity cssanimations csscolumns cssgradients cssreflections
csstransforms csstransforms3d csstransitions video audio localstorage sessionstorage
webworkers applicationcache fontface”>

In this example, you see such features listed as rgba and borderradius. This means Modernizr has found these features to be supported. Features that aren’t supported are prefixed with no-, as you see with geolocation (listed as no-geolocation here).

Let’s say CSS3 border-radius was the feature we were interested in. Let’s assume that rounded corners are particularly important to a site’s branding, and we want to provide a fallback for browsers that don’t support them. We could achieve that as follows:

.button {
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    border-radius: 5px;
}

.no-borderradius .button {
    background-image: url('rounded_corners.png');
}

In that example we took a graceful degradation approach. A progressive enhancement approach would look like the following:

.button {
    background-image: url('rounded_corners.png');
}

.borderradius .button {
    background-image: none;
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    border-radius: 5px;
}

We can even take a hybrid approach where we provide the common elements in both scenarios, and set the variations explicitly. Assuming we had grey text in Helvetica, a hybrid approach could look like this:

.button {
    font-family: "Helvetica Neue", Arial, sans-serif;
    color: #999;
}

.borderradius .button {
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    border-radius: 5px;
}

.no-borderradius .button {
    background-image: url('rounded_corners.png');
}

Which approach you choose depends largely on preference and standards that may exist in the team you’re working with. Regardless of the approach, Modernizr will help you.

Modernizr with SASS

CSS preprocessing is fantastic. It allows you to write less code that’s easier to maintain and more enjoyable to write. It can also help you produce better quality code. SASS seems to be the most popular CSS preprocessor, and the one we use at Domain7. For the basics of SASS visit their website.

One nifty feature of SASS is that it allows us to place a class above nesting using an ampersand. For example, the following

.selector {
    .page & {
        ...
    }
}

would compile like this:

.page .selector { ... }

This is incredibly useful with SASS because the Modernizr classes are applied to the html element, outside of our nested selectors. With the ampersand in SASS, we can rewrite the previous example like this:

.button {
    @include border-radius(5px);
    .no-borderradius & {
        background-image: url('rounded_corners.png');
    }
}

This allows us to write more maintainable code by encapsulating all of an element’s variations and fallbacks into a single nested declaration block. We’ve also used Compass in this example.

Using Modernizr in JavaScript

As well as adding CSS classes to the html element, Modernizr also stores its findings as boolean values in a JavaScript object called, surprise surprise, Modernizr. If we want to run a JavaScript function only if the device supports touch events, we could write a conditional like this:

if ( Modernizr.touch ) { ... }

All of the tests for which we got an HTML class are available in this object. For example, borderradius from the previous example would be stored as a boolean value in Modernizr.borderradius

Modernizr.load

Aside from writing conditionals, Modernizr includes Modernizr.load (previously known as yep/nope) that allows us to load scripts depending on the results of a Modernizr test.

If we wanted to load a script only if our device supported HTML5 geolocation, and load a fallback if it didn’t, we could do so like this:

Modernizr.load({
    test: Modernizr.geolocation,
    yep: 'geo.js',
    nope: 'geo-polyfill.js'
});

We can also augment Modernizr.load with scripts that load in both cases, and a callback, like so:

Modernizr.load({
    test: Modernizr.geolocation,
    yep : 'geo.js',
    nope: 'geo-polyfill.js',
    both: 'all_the_things.js',
    complete: function() {
        // Initialization code
    }
});

Media queries

Although I’m a big fan of using harvey.js for media query-specific JavaScript, Modernizr can be used for this as well using the .mq() method. If we wanted to run a function just for small screens, we could do so like this:

if ( Modernizr.mq('only all and (max-width: 520px)') ) {
    smallScreen();
}

Note that we passed Modernizr the same media query we’d use in CSS.

Customizing Modernizr and finding more information

I mentioned earlier that we could customize Modernizr. When you go to modernizr.com, you can choose the production option and select just the tests you’ve used in your project, keeping your JavaScript light. When I’m developing a site, I’ll keep a running list of Modernizr tests I’ve used so I can create this lightweight production build very easily.

For more detailed information on using Modernizr, visit their documentation.

This post was originally written for the Domain7 blog.