Tags: web-dev concept

Progressive Enhancement

Date: 2026-08-17


Build a working baseline, then layer capability on where it exists. The argument that used to be made — “some users disable JavaScript” — was always weak; the real one is that JavaScript fails for everybody sometimes, and a baseline that works is what turns a failure into a slower experience rather than a blank page.


Progressive enhancement is building the core function of a page in HTML first, then adding CSS and JavaScript as layers that improve it where supported and aren’t required for it to work.

The layers

HTML       content and semantics. works everywhere, always
  ↓
CSS        presentation. an unsupported property is ignored,
           the page still works
  ↓
JS         behaviour. an error here should degrade, not destroy

Each layer assumes the one below it and enhances it. The test: remove the top layer and ask whether the task is still possible — slower or uglier, but possible.

The argument that actually holds

Not “users with JavaScript off”. That population is tiny. The real one is that the JavaScript-dependent path has many failure modes and they’re all common enough to matter:

a CDN request times out on a train
a third-party script throws and takes the bundle's error handler with it
an ad blocker matches your bundle's filename by accident
the browser is two versions behind and one syntax feature isn't supported
a corporate proxy mangles the response
the device runs out of memory on a long session
a deploy ships a broken chunk for four minutes

None of those are the user choosing anything, and every one of them turns a JavaScript-only page into nothing at all. On a server-rendered page with working forms, each is a degraded experience instead — Graceful Degradation.

The second argument, which is the commercial one: crawlers, previews and assistive technology all consume the baseline. A page whose content requires JavaScript execution is a page that’s more expensive to index and easier to get wrong — Rendering and SEO.

What it looks like in practice

Forms are the clearest case, and the highest-value one in commerce.

<!-- baseline: a real form. works with no JavaScript at all -->
<form action="/basket/add" method="post">
  <input type="hidden" name="sku" value="AB-123">
  <label for="qty">Quantity</label>
  <input id="qty" name="quantity" type="number" value="1" min="1" required>
  <button type="submit">Add to basket</button>
</form>
// enhancement: intercept, submit in the background, update in place
form.addEventListener('submit', async (e) => {
  if (!window.fetch) return;              // let the normal submit happen
  e.preventDefault();
  const res = await fetch(form.action, { method: 'POST',
                                         body: new FormData(form) });
  if (!res.ok) return form.submit();      // fall back to a real submission
  updateBasketCount(await res.json());
});

Two things make this genuinely progressive: the form works if the script never loads, and it falls back to a real submission if the enhanced path fails. An enhancement that fails to a broken state hasn’t enhanced anything — Forms.

CSS enhances the same way, without feature detection in JavaScript:

.grid { display: flex; flex-wrap: wrap; }        /* baseline everywhere */
 
@supports (container-type: inline-size) {         /* enhancement */
  .grid { container-type: inline-size; }
}

The distinction from graceful degradation

Related, routinely conflated, genuinely different:

PROGRESSIVE ENHANCEMENT          GRACEFUL DEGRADATION

start from a working baseline    start from the full experience
add capability upward            define what's shed when things fail

about BROWSER CAPABILITY         about RUNTIME FAILURE
decided at build/design time     decided per dependency, at runtime

"works without container         "reviews service is down →
 queries"                          render the page without reviews"

They meet in one place: a server-rendered page with working forms degrades gracefully by construction, because the client-side dependencies were never load-bearing — Graceful Degradation, Rendering Strategies.

The honest limits

Being absolutist here produces bad engineering, and it’s worth naming what genuinely can’t be done this way:

  • A real-time collaborative editor has no meaningful HTML-only baseline
  • A complex configurator — a made-to-measure builder with live 3D — degrades to a form and a phone number, and that may be an acceptable baseline or may not be worth building twice
  • An internal admin tool for twelve known users on known browsers has a different cost-benefit entirely

The rule that scales: the core commercial path degrades, the peripheral experience doesn’t have to. Browse, view a product, add to basket and check out should work without JavaScript on a retail site. The size-recommendation widget, the 360° spin and the live chat need not.

Where the frameworks landed

The mainstream framework position has moved back towards this — server rendering by default, progressive enhancement of forms, and shipping less JavaScript — after roughly a decade of client-rendered defaults. The vocabulary differs by framework but the shape is the same: render on the server, enhance on the client, and treat the enhancement as optional.

Worth noting because it means progressive enhancement is no longer a position you have to argue for against your tooling — it’s increasingly the path of least resistance — Hydration, Islands Architecture.

Where it interacts

  • Graceful Degradation — the runtime counterpart, and the more commonly needed of the two
  • Browser Compatibility — how to decide what’s baseline and what’s enhancement, from real support data
  • Rendering and SEO — the crawler is the most demanding consumer of the baseline
  • Forms — the single highest-value place to apply this on a commerce site