Progressive enhancement does not mean "works when JavaScript is disabled"

This topic came up last Sunday when we were recording an episode for WeCodeSign, but the episode will take two weeks to be published, and it will also be in Spanish, so unless you are able to follow my quickly spoken Spanish, you will miss my Important Insights™ on this topic. Which is why I'm writing about it here.

Apparently there's a misconception in which progressive enhancement has been fully equated with "works when JavaScript is disabled". That is not the case, but I could see how someone who hasn't been programming their entire life and is lost on the fire between the two sides will be really confused.

So, yes, progressive enhancement does mean that "it works without JavaScript" (generally). But it also means many other things. It means building in layers, and making the most of each layer to make the experience nicer to the users of a website.

You need to understand that the Web is not about pixel perfect results in standard screen sizes for a reduced subset of devices. It is also not about putting content, functionality and style in a giant blob of JavaScript and indiscriminately delivering it to every user. If you attempt to build websites this way, your websites will break, and you'll be at a loss as to why.

The Web is about malleability and adaptability, providing sane defaults, and letting user agents do their job.

A user agent is a browser, but it could be a search engine.

Get in the way of the user agent, and cause pain for everyone: the users and you.

To avoid making your life difficult, embrace building in layers, and embrace building for the unexpected.

This is how:

You start with the lowest layer: HTML.

HTML is about the content and the core functionality. The idea is that a website can deliver the service without, yes, JavaScript, but also without CSS.

This is feasible in most of the cases—the limits of this are for another discussion. Let's assume that you are or you have a great product designer, and you're able to identify the core service that you want to deliver.

The good news for you is that HTML5 is very powerful. Modern day browsers have an amazing array of built-in features that we could only dream of a few years ago. And the best thing is that they work across browsers, and you do not have to reimplement and distribute that code. Additionally, you are already benefiting from all the work on accessibility and usability that browser makers have already put on browsers. You will let users access your website in the way that is most comfortable to them with their favourite device and operating system, and you do not have to do anything. It is already done for you.

Not reinventing the wheel lets you move faster. It also lets your users in slow connections get to your content and services faster, which means happier clients.

Exercise: ask yourself questions like...

  • Does a hospital need a fancy pull down menu which elderly users cannot operate because they're not as physically coordinated as they used to, or would it rather benefit from a better information architecture that shows a big phone number and lets the elderly ring the hospital by clicking on it? (if accessing the page with a phone who knows how to deal with tel:// protocols)
  • Does a restaurant need a multiples of megabyte-sized intro which you cannot access on a slow roaming connection, or does it need to show a menu and a booking form?
  • Does a train operator need to link to a PDF timetable, or would it be better for the users to provide an HTML table with the times of the trains?

Notice how I said "if the phone knows how to deal with tel:// protocols". If you build using standards, user agents that recognise the standard will augment your content with additional features. This is progressive enhancement within the HTML layer. Making it better for the user.

Second step: CSS

Granted, a bare HTML page might not be the most attractive or differential feature for a business. If we just used bare websites it would be complicated to distinguish between services. We can add an extra layer: CSS.

Modern CSS has also really mindblowing features, just as its HTML5 counterpart. We can build truly beautiful and amazing layouts, with impressive effects and typography, nowadays, with just a few lines of code. We can transform bare HTML pages into incredible explosions of colours and graphical rhythm.

We can, but that doesn't mean your CSS is going to be displayed in the same way you had in mind. CSS works a bit like HTML, in layers. There are some initial safe default values that browsers set for users, and then developers can change them with CSS. But you have to keep in mind that principle of inheritance, and building on what there is already there.

You also have to keep the progressive enhancement principle in mind when you write CSS. Most browsers support CSS, but sometimes it might be the case that your browser cannot load parts of the stylesheet (like external fonts), or it doesn't know or doesn't want to interpret certain features, such as background gradients.

If you write CSS properly, you will define properties in a way that takes advantage of the way properties are interpreted, where the browser will ignore properties and values it doesn't understand. This makes it possible for pages to become richer as the browsers get better, but still provides functionality to everyone.

For example: if you define a background with a gradient, you should also define a flat background colour for browsers which will ignore the gradient (e.g. Opera Mini). Similarly, set a default background colour even if you're using an image as the background, so the browser has something to show your users if the image takes too long (or doesn't load at all!). Otherwise, you risk the case that your content is impossible to read, frustrating your users.

The final layer: JavaScript

Once you have HTML and CSS working together, you can start adding JavaScript functionality to augment the core functionality even more, but you need to be careful that it is an actual enhancement and not just an "enhancement".

Again, you need to ask yourself what is good for the user. And be open to uncertainty, and write your JavaScript with that in mind.

If you assume that your code is going to be running in a specific version of a browser running on a given platform, your code will break. To avoid this, don't assume the Web API you want to use is available. Do feature detection, and provide alternatives when things are not possible. At the very minimum, let the user know what is happening. Your JavaScript code should be written with this concept of progressive enhancement in mind.

Use the best platform features if they are available, to make the website even better: if Service Workers are supported, use them! If it makes sense, make the content available offline. Maybe even pre-fetch content. Store those custom fonts offline. Make the website fly and be delightful for users.

Another example: if you're making calculations, offload them to a Worker so the UI is responsive. A train company might want to not only show static time tables but offer a way to sort, search, and maybe build an itinerary client side. It could even work offline. There are so many examples of good JavaScript patterns...! But this is not the place to discuss them.

It's not just 'a minority of crazy geeks' who disable JavaScript, but many users who involuntarily end up not loading JavaScript when the request times out in their slow mobile connection. Or the browser might stop your script if it makes things slow. There are many ways to this situation, and users might not even know what "JavaScript" is—they just know that your website doesn't work, and they are frustrated.

Conclusion: embrace building with layers

Try to fight the way the Web works, and cause trouble and misery for everyone.

Accept and build for the uncertainty of the networks, and the environment your website is accessed with, and you and your users will be happier. The code will last many years in good health.

Happy building for the Web! :-D