Introducing EnhanceJS: A smarter, safer way to apply progressive enhancement

April 2020 note: Hi! Just a quick note to say that this post is pretty old, and might contain outdated advice or links. We're keeping it online, but recommend that you check newer posts to see if there's a better approach.

As we discuss in our new book, Designing with Progressive Enhancement, and in previous articles, building with progressive enhancement is essential to ensuring a usable experience for all. But how do you determine which browsers should receive the enhanced experience and which should stick with the basic experience?

Introducing EnhanceJS, a JavaScript framework designed specifically to deliver a usable experience to the widest possible audience, by testing the browser to determine whether it is capable of correctly supporting a range of essential CSS and JavaScript properties, and delivering features only to those that pass the test.

We’re releasing EnhanceJS as an open source (MIT license) project to allow everyone to start building sites with test-driven progressive enhancement. In this article, we’ll review how to use EnhanceJS in your own projects so you can take advantage of new CSS and JavaScript features while ensuring a usable experience to all.

What is EnhanceJS?

Permalink to 'What is EnhanceJS?'

EnhanceJS is a new JavaScript framework (a single 2.5kb JavaScript file once minified/gzipped) that automates a series of browser tests to ensure that advanced CSS and JavaScript features will render properly before they’re loaded to the page, enabling you to build advanced, modern websites without introducing accessibility problems for people on browsers that aren’t capable of supporting all advanced features. It’s designed to be easily integrated into a standard progressive enhancement workflow: just construct the page using standard, functional HTML, and reference any advanced CSS and JavaScript files using EnhanceJS to ensure they’re only delivered to browsers capable of understanding them.

EnhanceJS is written with plain old JavaScript, so it has no dependencies, and works alongside other JavaScript libraries such as jQuery, Prototype and Dojo. The list of browsers that pass the default EnhanceJS test suite includes modern browsers back to Internet Explorer 6, Safari 3, Firefox 1.5, and mobile safari (iPhone); while browsers like Internet Explorer 5.5, Safari 2, and the Blackberry browser will receive the basic experience. The EnhanceJS test suite can be configured to meet the needs of any project, so the division of browsers will change depending on the capabilities you choose to test.

How does it work?

Permalink to 'How does it work?'

When integrated into your site, the EnhanceJS framework performs several important steps. The first time a person visits the site, EnhanceJS runs through a suite of JavaScript and CSS capabilities tests—such as box model support, floating, clearing, DOM manipulation, and Ajax.

If EnhanceJS confirms that the browser demonstrably supports all features, it applies additional CSS and JavaScript to the page to create a more enhanced user experience, applying them to the page in two ways:

  1. It assigns a class named enhanced to the html element. (Useful for small-scale enhancements to a page)
  2. It loads any JavaScript or CSS files that are specified into the page. (Useful for large-scale page enhancements)

If any single test fails, no enhancements are made, and the page is left as is: a fully functioning basic experience (provided, of course, that it was built with proper progressive enhancement techniques).

EnhanceJS then saves the browser’s pass or fail result in a cookie to prevent the framework from running through the capabilities tests at every page load. When EnhanceJS runs on subsequent pages, it first checks the cookie and then proceeds by adding enhancements (or not); if no cookie is found, it runs through the suite of tests again.

As a fallback option, the framework script appends a “toggle link” to the end of the page for users to manually switch between the basic and enhanced versions of the site. This level of user control is valuable for those occasions when a browser passes the tests and enhancements are applied, but something doesn’t work correctly.

screenshot of Filament Groups website homepage highlighting the
EnhanceJS toggle link

For a demo of a site using EnhanceJS, look no further than the page you’re reading right now! Assuming you’re in a browser with cookies enabled, just scroll to the bottom of the page, where you’ll see a “View low-bandwidth version” link. Clicking the link will toggle you from the basic to the enhanced version of this page. (If you see a “view high-bandwidth version” link, you’re viewing the un-enhanced version of the page, which means either your browser did not pass the test suite, or perhaps you already clicked the toggle link).

Sweet! How do I use it?

Permalink to 'Sweet! How do I use it?'

The first step in using EnhanceJS is to grab the lastest copy of the framework and reference it in your page. (You can grab the latest copy of EnhanceJS at the project page on Github, EnhanceJS.)

Once downloaded, reference EnhanceJS in a script tag in the head of your page:

<script type="text/javascript" src="enhance.js"></script>

With EnhanceJS referenced, you can run the test suite by calling the enhance function. This function accepts configurable options, such as arrays of JavaScript or CSS files that should be loaded in capable browsers.

<script type="text/javascript" src="enhance.js"></script>
<script type="text/javascript">
    enhance({
        loadStyles: [
            'css/enhancements.css'
        ],
        loadScripts: [
            'js/jquery.min.js',
            'js/enhancements.js'
        ]
    });
</script>

We’ve passed two options, loadStyles and loadScripts, to specify arrays of file paths to CSS and JavaScript files that should load in capable browsers. Stylesheets are dropped into the page and loaded immediately, while JavaScript files are loaded in a queued fashion to ensure script dependencies are loaded before they are called.

Note: When referencing files in arrays, be sure to separate each of the file path strings with a comma, and don’t add a trailing comma after the last item in an array.

In addition to the simple file path syntax shown above, the loadStyles and loadScripts options offer a more advanced format of referencing scripts and styles that allows you to set the attributes on the generated link and script elements. Using this syntax, you can easily specify a print-only stylesheet, for instance; just reference the file using object notation instead, with key/value pairs to specify any attributes you’d like. For example, the following code adds a print stylesheet on top of our “enhancements.css”, which is sent to all media types.

<script type="text/javascript" src="enhance.js"></script>
<script type="text/javascript">
    enhance({
        loadStyles: [
            'css/enhancements.css',
            {href: 'css/print.css', media: 'print'}
        ],
        loadScripts: [
            'js/jquery.min.js',
            'js/enhancements.js'
        ]
    });
</script>

In the example above, we’ve added a print-only stylesheet by setting the href attribute to the stylesheet’s file path, and the media attribute to 'print'—shown in bold—to ensure it will be directed to printers. When using this syntax, you can apply any attributes you’d like for both CSS and JavaScript references.

Print Stylesheet Tip: We typically reference a basic CSS file on the page from the start (basic.css) that contains some basic formatting such as the font family and useful text formatting rules, and then cascade our enhanced stylesheet rules off of those basic rules. The enhanced CSS files are referenced using the screen media type, so printers will only see the basic stylesheet rules which happen to serve as a clean print stylesheet. Try printing this page to see the result.

To make it easier on you, EnhanceJS will automatically set some required attributes, such as rel="stylesheet", type="text/css" for CSS references, and type="text/javascript" for JavaScript, so you don’t need to specify them unless you’d like to change their default values. The only required attributes when using this syntax are href when using loadStyles, and src when using loadScripts.

What about IE?

Permalink to 'What about IE?'

In the enhanced experience, you may want to direct scripts and styles to specific versions of Internet Explorer using conditional comments (while this practice is becoming less common, there are still many cases where websites direct fixes to IE). EnhanceJS provides a similar mechanism to let you specify scripts and styles that should only load in Internet Explorer by setting the ‘iecondition’ property, with either a version number like 6 or 7, or the string ‘all’ to direct it to any version.

<script type="text/javascript" src="enhance.js"></script>
<script type="text/javascript">
    enhance({
        loadStyles: [
            'css/enhancements.css',
            {href: 'css/print.css', media: 'print'},
            {href: 'css/ie6.css', iecondition: 6}
        ],
        loadScripts: [
            'js/jquery.min.js',
            'js/enhancements.js'
        ]
    });
</script>

Additional options

Permalink to 'Additional options'

In addition to loading styles and scripts, EnhanceJS has many other configuration options, such as changing the toggle link’s text (it defaults to “View high/low bandwidth version”), adding more tests to the default suite, and even overriding the test suite with your own set of tests. While our default test suite covers a range of basic capabilities that modern websites need, you might include additional tests for HTML5 features like the canvas, audio, and video elements.

For the full documentation, including the list of all options and their defaults, visit the EnhanceJS project wiki.

Did we mention there’s a book on this? For complete and detailed instruction on getting the most out of EnhanceJS, we recommend grabbing a copy of our new book, Designing with Progressive Enhancement.

How do I contribute?

Permalink to 'How do I contribute?'

If you have ideas for enhancements for EnhanceJS, or have found a bug, feel free to enter it on the project website under the “Issues” tab.

Speaking of contributors… We want to send a huge shout-out and thanks to Brandon Aaron, jQuery Core Developer and all around JavaScript guru, for his help in refactoring and contributing to EnhanceJS.

All blog posts