# Progressive Enhancement

> Canonical HTML version: https://rocketvalidator.com/glossary/progressive-enhancement
> Attribution: Rocket Validator (https://rocketvalidator.com)
> License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

Progressive enhancement is a web design strategy that starts with a baseline of functional, accessible HTML content and layers on CSS styling and JavaScript behavior so that all users receive a working experience regardless of their browser, device, or assistive technology capabilities.

Progressive enhancement treats content and core functionality as the foundation of every web page. A document begins as well-structured, semantic HTML that works in the simplest possible browser or screen reader. CSS is then added to improve visual presentation, and JavaScript is introduced last to provide richer interactivity. If any layer fails to load or is unsupported, the layers beneath it still function. This contrasts with "graceful degradation," which starts from a full-featured experience and tries to patch things up when capabilities are missing.

The strategy has a direct relationship with accessibility. When a page's meaning and operability live in the HTML layer, assistive technologies like screen readers and switch devices can reach all content without depending on scripts or styles. Keyboard users can navigate forms, links, and controls that exist as native HTML elements. Users on slow connections, older devices, or restrictive corporate networks get usable pages even when a JavaScript bundle times out.

## Why progressive enhancement matters

Screen readers parse the DOM. If a page's primary content or navigation is generated entirely by JavaScript and a script fails, screen reader users see nothing. Sighted users on a fast connection might not notice the problem, but the gap in experience is severe for people who depend on assistive technology.

WCAG 2.2 Success Criterion 4.1.1 (Parsing) and 4.1.2 (Name, Role, Value) both assume that content is expressed through markup that can be programmatically determined. Progressive enhancement naturally satisfies these criteria because the HTML layer already contains the structure, labels, and relationships that assistive technologies need.

Keyboard accessibility also benefits. Native `<a>`, `<button>`, `<input>`, and `<select>` elements are focusable and operable by default. When JavaScript adds custom widgets on top of these elements rather than replacing them with inert `<div>` and `<span>` elements, keyboard support comes for free.

Content reflow and responsive zoom are easier to handle as well. A page built on semantic HTML and layered CSS adapts to viewport changes because the content already has a logical reading order independent of visual layout. JavaScript-dependent layouts, by contrast, often break at unexpected zoom levels or viewport sizes.

## How progressive enhancement works

### The HTML layer

Every interactive feature starts as a native HTML element. A disclosure widget is a `<details>`/`<summary>` pair. A navigation menu is a `<nav>` containing a list of links. A form submits to a server-side endpoint using a standard `<form>` with `method` and `action` attributes.

### The CSS layer

Styling improves the visual experience but does not inject content or meaning. Layout techniques like flexbox and grid rearrange elements visually while the source order remains logical. Media queries and container queries adapt the presentation to different screens without altering the underlying document structure.

### The JavaScript layer

Scripts add convenience: client-side validation, animated transitions, dynamic filtering. They attach to existing HTML controls rather than replacing them. When a script replaces a native element with a custom one, it must replicate all the accessibility semantics (roles, states, keyboard handling) that the native element provided. Progressive enhancement minimizes the need for that extra work by keeping native elements in place.

## Code examples

### A toggle button without progressive enhancement (bad)

This example creates a button entirely through JavaScript. If the script fails, the user sees nothing interactive.

```html
<div id="toggle-container"></div>

<script>
  const container = document.getElementById("toggle-container");
  const btn = document.createElement("div");
  btn.textContent = "Show details";
  btn.style.cursor = "pointer";
  btn.addEventListener("click", () => {
    const panel = document.getElementById("details-panel");
    panel.hidden = !panel.hidden;
    btn.textContent = panel.hidden ? "Show details" : "Hide details";
  });
  container.appendChild(btn);
</script>

<div id="details-panel" hidden>
  <p>Additional information goes here.</p>
</div>
```

Problems with this approach:

- Without JavaScript, no toggle control is rendered at all.
- The `<div>` acting as a button has no implicit `role`, no keyboard focusability, and no accessible name beyond its text content.
- Screen readers do not announce it as an interactive control.

### The same feature with progressive enhancement (good)

Using the native `<details>` and `<summary>` elements, the toggle works without any JavaScript. CSS and scripts can layer on visual polish.

```html
<details id="info-disclosure">
  <summary>Show details</summary>
  <p>Additional information goes here.</p>
</details>
```

The browser handles open/close toggling, keyboard operation (Enter and Space), and exposes the correct ARIA roles automatically. If a design calls for animated expansion, JavaScript can listen for the `toggle` event on the `<details>` element and add a CSS class to trigger a transition, without removing the native behavior.

### A form that works without JavaScript (good)

```html
<form action="/subscribe" method="post">
  <label for="email">Email address</label>
  <input type="email" id="email" name="email" required>
  <button type="submit">Subscribe</button>
</form>

<script>
  const form = document.querySelector("form");
  form.addEventListener("submit", (event) => {
    event.preventDefault();
    // Send the data with fetch and show an inline confirmation.
    fetch(form.action, {
      method: form.method,
      body: new FormData(form)
    }).then(() => {
      form.innerHTML = "<p>Thanks for subscribing!</p>";
    });
  });
</script>
```

Without JavaScript, the form submits normally to the server and the user gets a full page response. With JavaScript, the submission happens asynchronously and the confirmation appears inline. Both paths are usable.

### A form that fails without JavaScript (bad)

```html
<div id="subscribe-widget"></div>

<script>
  document.getElementById("subscribe-widget").innerHTML = `
    <input type="email" id="email" placeholder="Email">
    <span onclick="subscribe()">Subscribe</span>
  `;

  function subscribe() {
    fetch("/subscribe", {
      method: "POST",
      body: JSON.stringify({ email: document.getElementById("email").value })
    });
  }
</script>
```

If the script does not execute, the user sees an empty page. The `<span>` is not keyboard accessible and has no button role. There is no `<label>` for the input, and no `<form>` element to provide a fallback submission path. This pattern violates multiple WCAG success criteria.

Progressive enhancement is not about avoiding JavaScript. It is about ensuring that JavaScript failure does not mean total feature failure. Start with HTML that works, then make it better.
