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

Posted by Scott on 02/09/2010


As we discuss in our new book, Designing with Progressive Enhancement, and in previous articles and presentations, 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?

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?

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?

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">
		loadStyles: [
		loadScripts: [

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">
		loadStyles: [
			{href: 'css/print.css', media: 'print'}
		loadScripts: [

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?

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">
		loadStyles: [
			{href: 'css/print.css', media: 'print'},
			{href: 'css/ie6.css', iecondition: 6}
		loadScripts: [

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?

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.

Other comments and questions about EnhanceJS are welcome below.

Book cover: Designing with Progressive Enhancement

Enjoy our blog? You'll love our book.

For info and ordering: Visit the book site


How about the html5 tests?

For example new elements like header, can that be implemented or is that not the meaning of this enhancment?

Comment by Daniel Langras on 02/18  at  06:24 PM

Hi Daniel,
EnhanceJS’s default test suite tests for a range of capabilities that we’ve determined to be necessary to render the average modern website, so HTML5 features are not tested by default. However, the test suite is customizable, so you can run tests for any capabilities that are essential to the functionality of a given site. Modernizr ( provides a great suite of tests for HTML5 features, and its tests could be integrated with EnhanceJS if you wanted to enhance a page based on proper support for HTML5 features.
Check out the addTests and tests options in the EnhanceJS documentation for info on how to customize the test suite:

Comment by Scott (Filament) on 02/18  at  06:42 PM

Nice article,

I’m using symfony framework, and symfony have their own way to load css and javascript, if some one already done how to use this enhancejs script properly with symfony, please.., share with us ...

Comment by Wildan Maulana on 02/19  at  02:38 PM

I have a need to generate some script at runtime and place this script inside of a script tag and I also use jQuery within this script (ie $(function()...)).  The issue is that in IE the $ is not defined yet because this script runs before the Enhance loads jQuery.js.  Is there a work around for this or am I doing something wrong?

Comment by Michael on 02/21  at  07:55 AM

@Wildan: We aren’t familiar with the symfony framework, but as long as you can edit the HEAD section of your page, you should have no trouble working with EnhanceJS. Just include a reference to the enhance.js JavaScript file, and reference all of your CSS and JavaScript files by passing them to the enhance() function, as shown above. This code can even be configured at the end of your enhance.js file if you can’t include a script block in the head of your page, though it’s best not to modify that file if possible.

@Michael: If I understand correctly, that sounds doable as long as the code that generates that script tag is in a file that’s included after jQuery (or if it is at the end of the jQuery.js file itself) to make sure jQuery is loaded. For example, using the code examples in this article, your script-generation code could work if it resided in enhancements.js (referenced after jQuery). Let me know if that helps.

Comment by Scott (Filament) on 02/23  at  02:22 AM

Do the scripts load in the order that is specified in the array

i.e. jquery before myscript.js (which is dependent on jquery i.e. starting a script with dom ready)



Comment by Adam on 02/25  at  06:14 PM

Hi Adam, yep they do. In fact, they each queue and wait for the previous script to finish loading, so dependencies won’t be used out of order. CSS files, on the other hand, are inserted in the order they appear, but they all get requested immediately, since CSS doesn’t have dependency error conditions to worry about.

If for some reason you have scripts that do not depend on each other, you can set the queueLoading option to false to make them all load immediately, but that’s not the default behavior of EnhanceJS.

Comment by Scott (Filament) on 02/25  at  06:35 PM

Progressive enhancement is the most valuable pattern web apps should adopt for better performance. For proof, turn off JavaScript in your browser and see how fast pages load. Progressive enhancement is the path to giving users a faster page render that then has increased functionality added to it.

So I’m excited to see this book and JS library for progressive enhancement. A few specific suggestions:

Whether to enhance a page is not a yes/no question. All of the JS and CSS might be applicable to browser X, none of it to browser Y, and some of it to browser Z. From the description here it sounds like browser Z is out of luck if it fails on just one of the EnhanceJS capability tests but passes all the others. The current yes/no approach might be too restrictive.

It would be better to suggest a pattern for loading enhance.js that doesn’t block the page (see ). A prerequisite for using enhance({loadscripts: blah}) is that the scripts do not have to be executed immediately (i.e., they do not use [removed]). I say this is a prerequisite because enhance() loads scripts dynamically so will not work if the scripts include [removed]. Because of this prerequisite, it would be fine to load enhance.js in a non-blocking way and have a callback to enhance().

One more non-blocking script enhancement would be in the implementation of appendScriptsSync. Here - your goal is to *evaluate* the scripts sequentially, but your implementation also enforces that the scripts are *downloaded* sequentially. These are two different issues. In fact, modern browsers (as of IE8, FF3.5, Saf4, & Chr2) have this improvement built in by default - if you include scripts using the normal SCRIPT SRC technique, the scripts are downloaded in parallel but guaranteed to evaluate in order. So enhance.js is actually a performance regression in this regard and will cause the page to be slower than if the scripts were included just using HTML (in these newer browsers). It’s more code, but there are ways you could implement this to download the scripts in parallel while still ensuring evaluation order. See my earlier blog post URL or checkout LabJS.

It’s likely that the way you’re appending stylesheets will block rendering in all versions of IE (see ). This is a tricky race condition to detect, but if you see it you could consider inserting a setTimeout.

Very nice job recording the “done” state in appendScriptsSync to avoid double execution in Opera. And you handle the setting of “head” nicely (although GA uses head || body instead of head || documentElement - ).

Comment by Steve Souders on 02/25  at  08:26 PM

Hey Steve, Thanks so much for chiming in. We love your work! And we’re glad to hear you’re excited about the book.

A few points on your comments:

- “From the description here it sounds like browser Z is out of luck”
While it’s true that the a call to enhance() will apply enhancements in an all-or-nothing fashion, there are a few points to clarify. First of course, “nothing” is not exactly nothing, but just a less-designed version of a perfectly usable page. When unsure, we don’t enhance the page, but all that means is a user might get a select menu instead of a slider control, or anchor links instead of tabs. That said, EnhanceJS can be configured to run entirely different tests than those included by default, and can even be used to do multiple experience divisions. For example, all browsers need to pass the default suite, but then we could run another suite to include some enhancements that require Canvas support, for example. We plan to post on these topics in followup articles.

“ it would be fine to load enhance.js in a non-blocking way and have a callback to enhance()”
Agreed. That sounds ideal, though in our own work we do appreciate running enhance immediately to key off of the html.enhanced class to prevent a FOUC. That aside, it was easiest for the purposes of this article to demonstrate loading EnhanceJS through a script tag, since there are so many other details to address, but we agree with your point. I’ll have to re-read your article to see if we might improve how we’re loading EnhanceJS on our site.

- “It’s likely that the way you’re appending stylesheets will block rendering in all versions of IE “
We haven’t experienced any problems with CSS rendering properly in IE, but we’ll definitely check out your article to see where we can make improvements to our implementation. It sounds like the timeout would be a good improvement.

- “you could implement this to download the scripts in parallel while still ensuring evaluation order.”
This is great advice. We plan to improve our loading implementation following the lead of scripts like lab.js.

“Very nice job recording the “done” state...”
Thanks. Hat tip to Brandon Aaron of jQuery for this part of EnhanceJS’s codebase. We’ll look into that last link on the order of head/documentElement.

Thanks again for all the feedback.

Comment by Scott (Filament) on 02/25  at  08:56 PM

Very nice, will have to test this method at the office.

Comment by Thomas Craig Consulting on 02/28  at  02:43 AM

After reading the response from Steve I looked at LabJS.  LabJS has a method “.wait()” and this is the functionality that I was trying to describe that I require.  This method will wait to execute a script until the required scripts are loaded before running the script and you can also pass a function into the method, very nice indeed.  Where this falls short of perfect is that it requires all scripts to be known when the Html Head is rendered because of the way they implemented this functionality.  I know that this sounds odd but I do not always know all the scripts that are required at this time. For example, if you are building a page and this page request a “widget” (any generic piece of functionality menu bar, image rotator etc...) via Ajax you do not know what scripts are required at page Head render time, only at widget render time do you know what scripts are required.  There is also the issue of not knowing what style sheets are required until the widget is rendered as well and LabJS does not load style sheet.  Anyway my particular issues aside, I think with a small modification you are able to achieve this “.wait()” functionality by modifying the createScriptTag function by placing the following code at the top of the function:
function createScriptTag(item) {
if (typeof item == ‘function’) {
return null;
} ...

And now you can do the following:
loadScripts: [

Seems to work for me but with your better knowledge of the code I would like your feedback if possible. 

And I just received you book in the mail and it looks to be very good!

Comment by Michael on 03/03  at  11:21 AM

Update: there’s a new Google Group for questions, bug fixes, and ideas for improving EnhanceJS. Check it out here:

Comment by Scott (Filament) on 03/03  at  04:14 PM

@Michael: It sounds like you’re talking about something similar to a discussion going on in the EnhanceJS Google Group. Check out this thread and see how the proposed features sound:

Comment by Scott (Filament) on 03/03  at  04:14 PM

Congratulations on a great book. I am currently redesigning my own site and have used your PH technique to great effect. I plan to have 2 versions of my site, mobile and desktop, is it possible to use the script to detect mobile browsers such as mobile safari for the iphone and switch the style sheet or even the url?

Comment by Nick Lansdell on 04/15  at  10:42 AM

@Nick: Thanks so much. We’re glad to hear you enjoyed the book!
We’re currently working on some different approaches to dividing out desktop and mobile-optimized enhancements using EnhanceJS, and we hope to post an update soon with our recommendations. If you are looking to simply load some different CSS for handhelds (for a small-screen optimized layout for instance), you might try configuring loadStyles to check available screen dimensions before defining the CSS arrays. Something like this maybe:

loadStyles:  screen.availWidth < 500 ? [’/css/mobile.css’] : [’/css/screen.css’]

I haven’t done a lot of testing on this approach yet, but it should work for simple stylesheet switches.

Comment by Scott (Filament) on 04/15  at  03:55 PM

@Scott. Many thanks for the reply I will certainly try this method for my standard mobile site. Could I use a similar technique if I wanted to switch url’s. I wish to create a mobile site using the JQTouch library and feel it be better to create a separate site for this and host it on a .mobi url.

Comment by Nick Lansdell on 04/15  at  04:19 PM


Since English is not my native language, i’m not sure i understand quite well the benefit of using enhance.js. i’m not that dumb usually :)

Correct me if i’m wrong, but i understand and see enhance.js as something similar than an “if” statement to load something specific when needed. Excepted that you can apply that “if” statement to any browser you want.
So you could, let’s say, load a css file with all the CSS3 styling only to the ones supporting it, right?

Could some one provide another example case showing why it would matter and be beneficial to use it?

thanks a lot.

Comment by Julien on 04/16  at  01:00 PM

@Julien: The goal of EnhanceJS is universal accessibility. You could use it for smaller feature divisions like that, but many CSS3 features such as border-radius don’t tend to cause usability issues when they fail to render properly, unlike floats, box model, etc. We commonly to use EnhanceJS to decide whether a browser should get a completely basic experience (functional HTML, very simple CSS), or all of the CSS and JS files to make a more enhanced version of the page. Click the “view low bandwidth version” link in our footer below to see how we use EnhanceJS. If you were in an older browser or handheld, that “low bandwidth” version may be how you’d see this site by default, and it would likely be more usable than if your browser had tried to render the more complicated CSS layout. There are other benefits too, such as faster page load when browsing on a low-featured mobile device.

I hope that helps clarify. If you want more background on the idea of test-driven PE, this A List Apart article from last year introduced the concepts that led to EnhanceJS:

Comment by Scott (Filament) on 04/16  at  02:02 PM

Hi Scott,

thanks a lot for your additional explanation and link. It’s more clear to me now.
Now, all this left is to try it out and see how i can apply on my project.

thanks again!

Comment by Julien on 04/17  at  05:43 AM

Hi Scott,
I see you have included a nice way to handle mobile and desktop stylesheets into EnhanceJS. This is a great addition and thank you for implementing this. I wonder if you could help with one thing. I am really struggling to add a ‘Mobile Version’ button to my site and wondered how you have managed to add this yourself and then wrap it in a div#toggleContain? Many thanks.

Comment by Nick Lansdell on 06/29  at  11:31 AM

Great framework! I’m wondering where the best place for support (if available) is? I have a question about this that may be better handled there.


Comment by Angie on 07/13  at  11:18 PM

@Angie - The best place for support is the Google Group for Enhance:

Comment by Todd (Filament) on 07/13  at  11:28 PM

@Nick: Thanks! We had just barely uploaded the mobile updates when you commented - nice catch :)

We plan to post a lab article soon detailing the approach, but for now, I’ll try and explain in brief. First, the file enhance.config.js is the place where we’ve linked up files for screen and mobile. You’ll notice at the top of the file, we define the screen media type differently depending on whether a mobile-specific media query ("screen and (max-device-width: 1024)") happens to apply in the current browser. If it does apply, it means we’re dealing with a newer mobile browser like iPad, iPhone, Android, Blackberry, etc, and we define our screen media type in a way that excludes those devices. If not, we just set it to “screen”. Once determined, we use these types to direct certain CSS and JS to only screen or only mobile, while some files may go to both as well. Check out to see how we’ve divided up the file references.

Once this division is made, you can use EnhanceJS’s toggleMedia method to pass two media types or queries you’d like to switch and refresh the page. For our site, we’re toggling between the screen and handheld media types, but you could toggle any you’d like. You’ll see at the top of both screen.js and handheld.js, we’re generating links that trigger that method and placing them alongside the lowfi/highfi toggle link:

Hope that helps - we’ll try and post that article asap!

Comment by Scott (Filament) on 07/13  at  11:59 PM

That does help a lot Scott and thank you for your reply. I have managed to implement this into my site nicely. Although on some older mobiles (Nokia) I have tested on I do not get the switch to the basic experience like your site does, also on iPhone Opera mini I do not the same screen width as your site does. I have probably messed something up in my CSS or JS somewhere as I am still finding my feet with this approach so I keep testing. Gotta say though this library is going from strength to strength - well done and I’ll look forward to the upcoming article.

One other thing how have you managed to concatenate your JS files together (/js/enhance.js,enhance.config.js)? Very clever stuff indeed!

Comment by Nick on 07/14  at  08:28 AM

Here’s the start of a method to enable developing multiple “full alternative” versions of browser-targeted stylesheets rather than the usual IE override technique. It uses diff, plus the Compass authoring toolset a “CSS meta-framework” (which in turn is based on Sass). This allows you to maintain ALL your style code in a single source file, and compile/output different versions of standard CSS by using variables, conditionals and other real programming features within your CSS. Feedback please - hansbkk [at] gmail

Comment by HansBKK on 07/17  at  06:14 AM

If you are developing with the CakePHP Framework and want to add PE with enhanceJS. Here is an example of how you can add it to your layout files:

// Progressive ehancements
$enhancements["loadStyles"][] = $this->Html->url( ‘/css/reset.css’ );
$enhancements["loadStyles"][] = $this->Html->url( ‘/css/style.css’ );
$enhancements["loadScripts"][] = “href: ‘“ . $this->Html->url( ‘/css/init.js’ ) . “‘, iecondition ‘lt IE9’”;
$enhancements["loadScripts"][] = $this->Html->url( ‘/js/jquery-1.4.2.min’ );
$enhancements["loadScripts"][] = $this->Html->url( ‘/js/enhancements.js’ );

echo $this->Javascript->object( $enhancements,
array( “safe” => false, “block"=>true, “prefix"=> “enhance(”, “postfix” => “)”, “quoteKeys” => false),
false );
echo $scripts_for_layout;

Comment by Michael Bourque on 08/20  at  03:06 PM

Firstly thanks for your great work!

I am having great difficulty getting this up and running though. :(

Is it at all possible for filamentgroup to create a very simple sample site with the other code as hosted on google?

I can get everything to work reasonably well, but when the page is first loaded, even if the cookie has been set correctly, it won’t get past this if statement in the appendToggleLinks function.

if (!settings.appendToggleLink || !enhance.cookiesSupported) { return; }

If I refresh the page it works fine and the links are appended.

I am on a mac using the latest versions of Chrome, Safari and Firefox.

Comment by Glen on 09/21  at  01:56 PM

Hi Glen,
I’ve noticed behavior similar to what you describe as well and have filed the issue in the tracker here:

However, this issue shouldn’t prevent the page from working entirely, so it sounds like something else is going on. Would you mind sending a link to your test page? If possible, a good place for this discussion is the EnhanceJS google group:

Comment by Scott (Filament) on 09/21  at  06:57 PM

Hi Scott,

Just checking out the Jquery Mobile alpha. Congratulations FG on your part this looks amazing. I was wondering if you have any plans to demonstrate how you would integrate this into EnhanceJS?

Also are you still supporting the Google Groups help for EnhanceJS? I have posted a few things and have no reply.

All the best.


Comment by Nick Lansdell on 10/20  at  12:16 PM

Congratulations on a great book. I am currently redesigning my own site and have used your PH technique to great effect.

Comment by basur on 11/02  at  11:00 AM

Wow this is a great function. As I view my website statistics, I cannot believe how many people still use IE6 and IE7. My site looks horrible on those browsers.

Comment by Peter on 05/09  at  05:56 PM

Commenting is closed for this post.

Book cover: Designing with Progressive Enhancement

Enjoy our blog? You'll love our book.

For info and ordering: Visit the book site