Graceful Degradation & Progressive Enhancement
Posted February 6th, 2007 by Tommy Olsson
----
Graceful degradation and progressive enhancement are two sides of the same coin. Both are --
in this context -- applied to make a web site accessible to any user agent, while providing
improved aesthetics and/or usability for more capable browsers. The difference between the
two is where you begin your approach.
Some time ago I wrote an article here about two very different approaches to web design:
Visual Vs. Structural. The concepts of graceful degradation and progressive enhancement
relate closely to those two approaches.
Graceful Degradation
Graceful degradation is the older of the two concepts. The term is also used in fields
other than web design, such as fault tolerant mechanical or electrical systems.
The premise for graceful degradation is to first build for the latest and greatest, then
add handlers for less capable devices. In other words, focus on the majority before
catering to those outside the mainstream. This is quite similar to the visual approach
of web design, where the first priority is to make it look good to most visitors.
A familiar example of graceful degradation is the alt attribute for images. When used
properly it provides a text equivalent that conveys the same information as the image
to users who cannot perceive the image itself. The text equivalent is most likely not as
aesthetically pleasing, and an image can say more than a thousand words, so the user
experience is slightly degraded.
Using layout tables may be seen as one form of graceful degradation: if the CSS styling
cannot be applied -- e.g., in really old browsers -- at least the basic page layout is
retained. But it doesn't work very well for text browsers like Lynx and some mobile
phone browsers, which do not support tables.
Another common occurrence in sites built from the graceful degradation point of view is
the noscript element. You provide some feature based on JavaScript and add a more basic
version for user agents that do not support JavaScript or have client-side scripting
disabled. One example could be the ubiquitous drop-down or fly-out menu:
Example One
The JavaScript function may create a menu where "Products" and "Services" have sub-menus
that drop down, fly out or expand when the user's mouse pointer hovers over the main
items. The non-JavaScript alternative provides immediate access only to the main items,
while the sub-menus are (presumably) included in similar noscript elements on the
respective interior pages.
This approach is designed for mainstream users -- those with a graphical browser that
supports JavaScript -- but it degrades gracefully so that it works in even the humblest
of text browsers. It also adds the benefit of proper HTML anchor links to non-JavaScript
user agents like search engine spiders, thus negating the critical SEO disadvantage of
JavaScript menus.
There is one problem with noscript, though. I may use a browser that supports JavaScript
and has it enabled, but there could be a company firewall that strips incoming JavaScript
for security reasons. In this case the noscript element will not be rendered (because my
browser supports scripting) but the JavaScript code that should create the menu won't be
applied either, because it gets stuck behind the firewall.
Not-So-Graceful Degradation
Unfortunately the concept of graceful degradation sometimes deteriorates way past the
point where the adjective "graceful" can reasonably be applied. The worst case is code
like this:
Example Two
This is unacceptable for any serious web site, but may be forgiven on a small amateur
site whose target audience is friends and family.
We, as web designers or developers, have no right to tell our visitors which browser they
"should" use; nor do we have the right to tell anyone to "enable JavaScript." We don't
know why this visitor uses Lynx or why that visitor has disabled client-side scripting.
They may not even have the option of "upgrading" or "enabling JavaScript" because of
bandwidth, hardware or budget constraints; IT department policy; or because they are not
even using their own computer.
Graceful degradation is far better than having a site that is completely inaccessible to
some users, but it is not the best approach. As with the visual design approach, it is
often difficult to begin with all the features and then remove them one by one, without
everything falling apart.
Progressive Enhancement
Progressive enhancement became known -- at least under that name -- in 2003, when
Steve Champeon began using it on Webmonkey and during the SXSW conference. It starts at
the opposite end from graceful degradation: begin with the basic version, then add
enhancements for those who can handle them. Again, comparing it to the design approaches:
this is the same basic thought as in structural design. That starts with the markup and
adds styling on top of that, which is progressive enhancement all by itself.
The most common occurrence of progressive enhancement is probably the external CSS style
sheet. It is ignored by non-CSS browsers -- which thus get only the plain markup and
render it according to their own built-in style sheets -- but it is applied by modern
graphical browsers, thus enhancing both the aesthetics and the usability for mainstream
and advanced users.
Other examples of progressive enhancement are the various image replacement techniques,
the Flash satay method and (sometimes) AJAX.
Progressive enhancement when it comes to JavaScript is becoming more common these days.
The key to this is known as unobtrusive JavaScript. An unobtrusive script is silently
ignored by user agents that do not support it, but is applied by more capable devices.
Just like an external style sheet.
Let us revisit the navigation menu we examined as an example of graceful degradation.
How would that be done using progressive enhancement?
We would begin by creating our markup, aiming for the lowest common denominator: HTML.
A navigation menu is, semantically, a list of links. The order of those links does not
affect the meaning of the list as a whole; thus it is an unordered list.
Example Three
This will work in everything from Lynx and Mosaic 1.0 to the latest versions of Opera,
Firefox and Safari. Googlebot and its arachnoid cousins will also love it.
The next step is to add enhancements for the vast majority of users whose browsers
support CSS. We add rules in an external CSS file to style the menu. After adding a
link element with a reference to the external style sheet, it looks a lot better to
most visitors, but it is in no way detrimental to those Lynx users or to the Googlebot.
(OK, they'll have to download a few more bytes, but it will be negligible even on a
slow dial-up connection or a GSM mobile phone.)
But we can enhance this further by adding drop-down or fly-out or expanding sub-menus
to the main items, using unobtrusive JavaScript. To reduce the amount of script code,
we begin by assigning ids to the list items:
Example Four
Then we create a separate JavaScript file with a couple of functions:
Example Five
function addProducts()
{
// Find the li element to which we will add a sub-menu
var parent = document.getElementById("products");
// Make sure it exists (fail silently)
if (parent) {
// Create a nested unordered list
var ul = parent.appendChild(document.createElement("UL"));
// Add the list items and links
var items = [ ["Blue Widgets", "blue"],
["Red Widgets", "red"] ];
for (var i = 0; i < items.length; ++i) {
var li = ul.appendChild(document.createElement("LI"));
var a = li.appendChild(document.createElement("A"));
a.href = "/products/" + items[i][1];
a.appendChild(document.createTextNode(items[i][0]));
}
}
}
The addServices() function would look very similar. Then we need to be sure that the
browser can handle those DOM functions:
Example Six
function createSubMenus()
{
// Make sure that the DOM functions we will use are supported
// (fail silently)
if (typeof document.getElementById != "undefined"
&& typeof document.createElement != "undefined"
&& typeof document.createTextNode != "undefined")
{
addProducts();
addServices();
}
}
This function makes sure that the main DOM functions used by addProducts() and
addServices() are supported by the browser. If not, the function does nothing; it
causes no harm and there will be no JavaScript error message or warning icon. This
approach is called object detection and is infinitely better than the old-school
browser sniffing scripts that stopped working as soon as a new browser (or a new
version of an existing browser) was released.
Finally, to get the browser to call those functions as soon as the page has loaded.
Example Seven
if (window.addEventListener) {
window.addEventListener("load", createSubMenus, false);
} else if (window.attachEvent) {
window.attachEvent("onload", createSubMenus);
} else {
window.onload = createSubMenus;
}
Note that this piece of code is not enclosed in a function. It will add an event
listener for the window's "load" event, calling our menu-creating function immediately
after the HTML document has finished loading. (We need to wait for that, so that the
id attributes are available.)
Modern browsers will use the addEventListener() function specified in the DOM Level 2
(Events) specification, while Internet Explorer will use its proprietary attachEvent()
function.
The last catch-all is not perfect: it will replace any existing onload handler
created by a previous script. That is hardly unobtrusive. In real life we would need
a slightly more complicated solution for this case, but I don't want to make the
examples unnecessarily complicated.
We will add a script element in the HEAD section of our document to load the external
JavaScript file:
As a final step we would add CSS for those sub-menus and JavaScript event listeners
to display or hide them, making sure to handle both mouse and keyboard activation, of
course.
Form Validation
Form validation is a common use for JavaScript, but one can obviously not rely on it.
If we did, someone who has disabled client-side scripting could submit invalid data,
and a nefarious script could abuse our form for who-knows-what.
Validating form data is something which must always take place on the server side, but
for efficiency reason we may wish to use client-side validation in addition to that. It
will benefit users by giving quicker feedback and it will benefit us because it reduces
the number of requests to our servers.
Using the methods outlined above, we can add our JavaScript validation unobtrusively
and with object detection. A modern browser with JavaScript enabled will enhance the
validation process, while we still retain our back-end validation for the script-
challenged minority (and to preserve our own mental health).
Ajax
AJAX became an important buzzword last year. Everyone must use it, whether they really
needed it or not. Sometimes I get the feeling that it is an answer in search of a
problem, but there is no denying that it is a useful technique for some common situations.
Consider a form with two or more select elements (drop-down lists or list boxes) where
each select is dependent on the previous one. Typical examples are drill-down choices
for type/subtype or country/state. Depending on what you select in the first list, the
content of the second list should change. How can we make this work as well as possible
for all users?
We will once again begin with the basics. Do not think about client-side scripting at
this stage; think only about HTML support:
Example Eight (in HTML)
Our primary list (Type) is populated with our product types: widgets and gadgets.
The secondary list (Subtype) is empty and disabled, and the main submit button (Show
Products) is disabled. A user needs to choose a product type and then activate the
other submit button ("Select"). The form is submitted to our server-side script
(products.php), which will then redisplay the form with the subtype list populated
with the subtypes of the selected type (or a friendly error message if no type was
specified).
The user can then select a subtype and submit the form using the "Show Products"
button (which is not disabled this time). If she changes her mind and wants to select
another type, she will have to activate the "Select" button again to load the correct
subtypes.
This is not the most user friendly form in the world, and it needs some rather tedious
(but straightforward) server-side scripting, but it works in virtually all browsers.
Even Lynx handles forms like this one.
But we would like to make it faster and easier to use. It would be nice to populate
the secondary list as soon as the selection changes in the primary list. This requires
JavaScript. More often than not the choices of types and subtypes are dynamic and need
to be retrieved from a database rather than being hard-coded, which makes this a likely
candidate for an AJAX solution.
We would use unobtrusive JavaScript to detect support for the necessary features (mainly
XMLHttpRequest). If supported, we would add an event handler for the primary list using
addEventListener() or attachEvent(), depending on the user's browser. Then we would remove
the "Select" button using the removeChild() DOM function, because it is no longer
necessary and would only be confusing.
The added event listener would open an asynchronous connection to our server and post
an HTTP request specifying the selected type. The server script would query the database
and return an XML reply containing the available subtypes, which we would then parse
and use to populate the subtype list (with DOM functions). Then we could enable the
subtype list and the main submit button.
A JavaScript-enabled browser that lacks support for AJAX could submit the form
automatically to a server-side script that would redisplay the form with a populated
secondary list, much like the non-scripted version but without requiring the user to
activate an extra button.
This is progressive enhancement: it works for everyone, but users with modern browsers
will see a more usable version. We are, in a way, rewarding them for choosing to use a
good browser, without being rude to Lynx users or employees of companies with paranoid
IT departments. We don't even have to trust the unreliable noscript element, and since
we would use object detection rather than browser sniffing we don't have to keep
updating the JavaScript for every new browser version.
Choosing a Method
Both graceful degradation and progressive enhancement obviously assist in making a website
accessible, yet providing additional usability for those who can take advantage of it.
So which one should we choose?
Progressive enhancement is usually preferable to graceful degradation, for the same reason
that structural design usually leads to better accessibility than visual design: it starts
with the simple basics and adds embellishments on top of that. When designing something
from scratch, we should definitely think in terms of progressive enhancement.
If we are maintaining an existing site, trying to improve accessibility and standards
conformance, the situation is different. Unless we want to rewrite everything, our only
choice is to provide graceful degradation as best we can.
Of course it is also possible to mix both methods on one site, or even on the same page.
Progressive enhancement is what we should strive for, though, if we have a choice.
Testing
Testing the accessibility is much easier with progressive enhancement than with graceful
degradation.
If we work from the point of view of progressive enhancement, we can simply create the
basic version and verify that it works. Then we add the enhancements and verify that
they work. Okay, this is somewhat simplified: when testing the enhancements we may
need to verify that they don't break the basic version.
If we use graceful degradation (or when we verify that added progressive enhancements
don't cause problems in less capable user agents) we need a different approach. Now
the advanced features are already present and can be readily tested. To verify that
they really do degrade gracefully, we must disable the support for those features.
Sometimes that will require testing in a different browser, but some things -- like
text equivalents or JavaScript/plug-in support -- can be checked using the
accessibility features of Opera or with the Web Developer Toolbar in Firefox.
Besides...
Accessibility evangelists like myself are sometimes accused of being reactionary zealots,
new Luddites who abhor anything that makes the Web more enjoyable and entertaining.
Some of those who disagree with us claim that we do not allow JavaScript or Flash to be
used on a website.
I hope this article will prove them wrong once and for all. Anything that improves
usability or aesthetics is a good thing. What I (and many others) wish to point out
is the danger of relying on non-standard, proprietary or platform-dependent technology.
All that should be required for accessing information on the Web is a user agent that
supports HTTP and HTML. Such a user agent should be able to access the vital
information on any professional site.
If the user agent supports CSS, JavaScript, Flash, MathML, SVG, embedded video and so on,
that is great! Those users will benefit by seeing a better-looking site which is also
easier to use. There may even be non-critical content that won't be accessible to that
bare-bones browser, but which the more advanced users can enjoy.
Graceful degradation, to some extent, but especially progressive enhancement make this
dichotomy possible. It is possible to eat your cake and have it, too. It requires a
bit more work, sometimes, but it is often worth it.
---
Copyright (c) 2007, Accessites.org
http://accessites.org/site/2007/02/graceful-degradation-progressive-enhancement/