Progressive Enhancement

Progressive enhancement is a strategy that starts with a working baseline of semantic HTML and then layers on CSS and JavaScript as optional improvements, so the page still functions even when styles or scripts fail to load.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free HTML & CSS course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

By the end of this lesson you'll be able to build a page from a rock-solid baseline up — semantic HTML that works with no CSS or JS, then optional CSS and JavaScript layers gated by feature checks — so no visitor ever gets a broken experience.

Progressive enhancement is a building with stairs first, then an escalator, then a lift. The stairs (semantic HTML) let everyone reach every floor, always. The escalator (CSS) makes the trip smoother. The lift (JavaScript) makes it effortless.

Crucially, you build the stairs first and they never depend on the lift. If the power cuts out, the lift stops but people still get upstairs. A badly-built site does the opposite: it installs only a lift, so the day the power fails, nobody can move at all. That is the difference between enhancing a working base and betting everything on the top layer.

Progressive enhancement is a philosophy: build the most basic, universally-supported version first, then add layers for browsers that can handle them. The baseline is semantic HTML — real headings, real links, real forms — that is readable and usable before a single byte of CSS or JavaScript arrives.

The test is simple: would this page still work with CSS and JS switched off? A a href still navigates. A form method action still submits to the server. That guaranteed floor is what every later layer stands on. Run this stripped-down page — it has no styles and no scripts on purpose.

Once the baseline works, you layer styling on top — but only where it is safe. The tool for that in CSS is the feature query , @supports . It works like a media query, but instead of asking about the screen it asks about the browser's capabilities : @supports (display: grid) {' … '} applies the enclosed styles only if the browser understands that exact declaration.

This is the heart of an enhancement with a fallback. Write the version that works everywhere outside the block (here, flexbox wrapping), then put the upgrade inside the @supports block (CSS Grid). A browser without Grid simply ignores the block and keeps the flexbox base — no broken layout, no error.

You can also test selectors with @supports selector(:has(*)) , combine conditions with and / or , and negate with not — e.g. @supports not (display: grid) to target browsers that lack a feature.

The whole approach falls apart the moment a higher layer becomes required . If your only "Read more" control is a div wired up with a click handler, then a visitor whose JavaScript failed to load — flaky network, ad blocker, an extension, a crawler — gets nothing. The fix is to make the baseline the working version and let JS enhance it.

HTML gives you genuinely interactive elements that need no JavaScript at all : details / summary for an accordion, dialog for a modal, and a real form for submissions. Reach for these first; only add JS when you need to go beyond what they do.

These are two routes to the same destination — a site that works for everyone — but they start at opposite ends. Progressive enhancement builds up: baseline first (HTML), then style (CSS), then behaviour (JS), each layer optional. Graceful degradation builds down: create the full modern experience first, then add fallbacks so older browsers do not collapse.

Modern best practice favours progressive enhancement because the working version comes first — you can never forget to add a fallback, because the fallback is the starting point. With graceful degradation, every missing fallback is a potential broken page. Think of it as the three-layer model: HTML (content) → CSS (presentation) → JavaScript (behaviour) , where each layer enhances the one below and none replaces it.

Build up from a solid base. "Start simple, enhance for capable browsers." The baseline is guaranteed.

Fall back from the full experience. "Start complex, add fallbacks for limited browsers." Easy to miss one.

A frosted-glass panel should upgrade only where backdrop-filter is supported, leaving a safe semi-transparent base everywhere else. Fill in the blanks marked ___ , then run it and check the expected result in the comments.

One "accordion" needs JavaScript to open, and one layout uses a feature you can't assume. Swap the JS-only widget for a native element pair, and wrap the upgrade in a feature query so old browsers keep the base. Verify the expected behaviour in the comments.

Support is faded now — only an outline is given. Build a small page with all three layers: a semantic HTML baseline, a flexbox CSS base, and a Grid enhancement gated by @supports . Use the worked examples in sections 1 and 2 as your reference if you get stuck.

You can now build pages that work for everyone and get better where the browser allows. The essentials:

Next up: Custom Scrollbars , where you'll polish the last bit of UI chrome.

Practice quiz

What is the foundation (baseline) layer in progressive enhancement?

  • A JavaScript framework
  • A CSS Grid layout
  • Semantic HTML that works with no CSS or JS
  • A service worker

Answer: Semantic HTML that works with no CSS or JS. Progressive enhancement starts from a working baseline of semantic HTML, then layers CSS and JS as optional improvements.

How do progressive enhancement and graceful degradation differ?

  • PE builds up from a baseline; graceful degradation builds down from the full experience
  • They are the same thing
  • PE is only for CSS; graceful degradation is only for JS
  • PE requires no HTML

Answer: PE builds up from a baseline; graceful degradation builds down from the full experience. Progressive enhancement builds up from a guaranteed baseline; graceful degradation starts complex and adds fallbacks.

What does an feature query test?

  • The screen width
  • The user’s connection speed
  • Whether JavaScript is enabled
  • Whether the browser understands a CSS property/value

Answer: Whether the browser understands a CSS property/value. @supports tests the browser's capabilities — e.g. @supports (display: grid) is true only if Grid is understood.

How does differ from ?

  • They are identical
  • @media tests the device/environment; @supports tests the browser’s feature support
  • @supports tests screen size; @media tests CSS features
  • Only @media can be nested

Answer: @media tests the device/environment; @supports tests the browser’s feature support. @media asks about the environment (width, scheme, print); @supports asks whether the browser supports a feature.

In an enhancement-with-fallback, where does the base (fallback) style belong?

  • Outside the @supports block
  • Inside the @supports block
  • Inside a @media block
  • In a separate @supports not block only

Answer: Outside the @supports block. The universally-safe baseline goes outside the @supports block; the upgrade goes inside it.

What happens to styles inside an block when the tested feature is missing?

  • They override the base styles
  • They cause a parse error
  • The browser ignores the whole block, leaving the base styles
  • They apply anyway

Answer: The browser ignores the whole block, leaving the base styles. If the feature is unsupported the condition is false and the block does nothing, so only the base styles apply.

Which native HTML elements give interactivity with NO JavaScript?

  • <div> and <span>
  • <details>/<summary>, <dialog>, and <form>
  • <script> and <link>
  • <canvas> and <svg>

Answer: <details>/<summary>, <dialog>, and <form>. <details>/<summary> (accordion), <dialog> (modal), and a real <form> work without any JavaScript.

Why should critical content never depend solely on JavaScript?

  • JS is slow to write
  • JS is not supported in any browser
  • HTML cannot hold text
  • JS can fail (flaky network, blockers, crawlers), leaving users a broken page

Answer: JS can fail (flaky network, blockers, crawlers), leaving users a broken page. Scripts fail more than expected; if content or core actions only exist after JS runs, those users get nothing.

Can detect a JavaScript API like IntersectionObserver?

  • Yes, @supports (IntersectionObserver)
  • No — @supports is CSS-only; use JS feature detection like 'IntersectionObserver' in window
  • Yes, with @supports script()
  • Only in older browsers

Answer: No — @supports is CSS-only; use JS feature detection like 'IntersectionObserver' in window. @supports tests CSS only; for JS APIs use feature detection such as 'IntersectionObserver' in window.

Which condition targets browsers that LACK a feature?

  • @supports (display: grid)
  • @supports no (display: grid)
  • @supports not (display: grid)
  • @supports !display: grid

Answer: @supports not (display: grid). @supports not (display: grid) is true only when the browser does not support that declaration.