Chapter 06 of 09Part 2 · Layout
Progressive enhancement: what JavaScript is for
Make the page work with HTML first, then let JavaScript add extras. What scripts should and should not be trusted with.
8 minBy Zuzo StudioUpdated
In this chapter you will
- explain progressive enhancement as layers of HTML, CSS and JavaScript
- decide which jobs belong to HTML and which to JavaScript
- write a small script that checks for support before it adds a feature
Before you startThis builds on Responsive web design, from phone to wide screen.
So far your page is HTML and CSS, and it already works: people can read it, follow its links and get in touch. JavaScript is the third layer, and the one that is easiest to overuse. Progressive enhancement is the idea that keeps it in its place: build the page so its core job works with HTML alone, make it look right with CSS, then use JavaScript to make it better where the browser can handle it. Nothing essential should depend on a script arriving. This chapter covers what that means in practice, what JavaScript is good for, and what it should be kept away from.
01What progressive enhancement means
MDN's glossary entry on progressive enhancement defines it as a design philosophy that gives a baseline of essential content and functionality to as many people as possible, while delivering the best possible experience to browsers that can run all the code. The baseline comes first. The extras are layered on top.
Think of the page in three layers:
- HTML carries the content and the actions. Text, headings, images with alt text, links that go somewhere, forms that submit. If only this layer arrives, the page still does its job.
- CSS makes it readable and pleasant: the layout, type and colour from the last three chapters. If a newer CSS feature is not supported, the browser skips that rule and the page still works.
- JavaScript adds behaviour on top: copying a value with one click, filtering a list without reloading, a menu that opens smoothly. If it fails, the page loses a convenience, not its purpose.
The opposite approach, sometimes called graceful degradation, starts from the full experience and patches in fallbacks for weaker browsers. Both aim at the same goal, but starting from the baseline is easier to get right, because the fallback is the first thing you build rather than the last thing you remember.
02Why JavaScript fails more often than you think
It is tempting to assume every visitor runs your scripts. Most do, most of the time. But "JavaScript is off" is the rarest reason a script does not run. The common ones are much more ordinary:
- The file is still downloading on a slow mobile connection, and the visitor taps a button before it arrives.
- A script further up the page throws an error, and everything after it stops.
- A browser extension, a corporate network or a content blocker removes a third party script the page relied on.
- The device is old or busy, and a large bundle takes seconds to run.
Size makes all of these worse. The HTTP Archive's 2024 Web Almanac JavaScript chapter found that the median mobile page loads 558 kilobytes of JavaScript across 22 requests, and that at the median on mobile, 44% of the JavaScript bytes delivered are not used during page load. Every one of those bytes has to be downloaded, parsed and run on the visitor's device, and while the browser's main thread is busy running scripts, it cannot respond to taps. That delay is what the responsiveness measure in the website speed chapter picks up.
03What JavaScript should and should not do
A simple test: if this script never ran, could someone still do what they came for? If the answer is no, the job belongs to HTML, or to the server, not to the script.
| Job | Who should do it | Why |
|---|---|---|
| Showing the main text and headings | HTML | Search engines, screen readers and slow phones all get it straight away |
| Going to another page | A real <a href> link |
Works with a keyboard, opens in a new tab, survives a broken script |
| Sending a form | A <form> with an action |
Submits without scripts; JavaScript can add checks and a nicer success state |
| Hover, focus and simple transitions | CSS | No script to load and no main thread work |
| Show and hide a block of text | <details> and <summary> |
Built in, keyboard friendly, no code |
| Copy to clipboard, live filtering, a map | JavaScript | Genuine extras the page works without |
Use what the browser already does
Many things that used to need a script are now built into HTML and CSS. A disclosure, the open and close pattern often used for FAQs, is one element. MDN's details reference also covers the name attribute, which groups several of them so that opening one closes the others, the accordion behaviour people used to write code for.
<details>
<summary>What does the studio do?</summary>
<p>Brand websites, from the first sketch to launch.</p>
</details>
Before you add a library for something, check whether the platform does it already. It is less to download, less to break and less to maintain.
04Enhance in layers: detect, then add
When you do reach for JavaScript, write it so it checks first and only adds a feature when the browser supports it. This is feature detection: you test for the thing you need, not for a browser name.
if ('share' in navigator) {
// show a Share button that opens the phone's share sheet
}
CSS has the same idea built in with @supports, which applies rules only when the browser understands a property:
@supports (container-type: inline-size) {
.card-list { container-type: inline-size; }
}
Two loading habits matter just as much. Put your script tag in the <head> with defer, which tells the browser to download it alongside the HTML and run it only once the page has been parsed, so it never holds up the first paint. And let the HTML do the first render: the content should be in the page you send, not built by a script after it arrives.
<script src="script.js" defer></script>
Respect what people have asked for
Enhancement also means listening to the settings people have chosen. Some people turn on a reduced motion setting on their device, often because animation makes them feel unwell. The prefers-reduced-motion media feature lets your CSS, and your scripts through matchMedia, see that setting:
@media (prefers-reduced-motion: reduce) {
* { scroll-behavior: auto; transition-duration: 0.01ms; animation-duration: 0.01ms; }
}
We go further into motion that stays fast and kind in our website animation performance post, and into the wider set of needs in accessibility basics.
05Adding your first script
Your contact section has a link like <a href="mailto:you@example.com">. It already works without any code: clicking it opens an email app. Now add a small enhancement on top, a button that copies the address, for people who use webmail and find mailto links annoying.
const link = document.querySelector('a[href^="mailto:"]');
if (link && navigator.clipboard) {
const button = document.createElement('button');
button.type = 'button';
button.textContent = 'Copy email';
button.addEventListener('click', async () => {
await navigator.clipboard.writeText(link.href.replace('mailto:', ''));
button.textContent = 'Copied';
});
link.after(button);
}
Browsers only offer the Clipboard API in secure contexts, such as pages served over HTTPS. Most also treat a file opened from your own computer as secure, but if the button does not appear for you, that is the feature check working as intended, and it will show once the page is live over HTTPS in the launch chapter. With the page built, the next part of the course is about quality: whether everyone can use it, and whether it is fast.
Check your understanding
3 questions
Key takeaways
- Build the core job of a page in HTML, style it with CSS, and use JavaScript only to make it better.
- Scripts fail for ordinary reasons, like slow connections, errors and blockers, far more often than because JavaScript is switched off.
- Links go somewhere and buttons do something; a div with a click handler is neither.
- Use feature detection and @supports to add a feature only where the browser can handle it.
- Load scripts with defer so they never hold up the first render of the page.
Sources
- Progressive enhancementMDN Web Docs, checked
- JavaScript | 2024 | The Web AlmanacHTTP Archive, checked
- <details>: The Details disclosure elementMDN Web Docs, checked
- prefers-reduced-motionMDN Web Docs, checked