Progressive enhancement
Progressive enhancement means building a page that works with plain HTML first, then layering CSS for presentation and JavaScript for convenience. If scripts fail, slow down, or are turned off, the content should still be usable. This is not nostalgia – it is how you avoid locking people out and how you keep sites maintainable.
The baseline: HTML that works alone
Start by asking what the page must do without any enhancements. A registration form should submit. A product listing should link to product pages. A FAQ should expose the questions and answers. If the answer depends entirely on JavaScript, you have a JavaScript application, not an enhanced document – which may be fine for a dashboard, but is a poor default for content sites.
Forms without JavaScript
A form with action, method, labelled fields, and a submit button works everywhere:
<form action="/subscribe" method="post">
<label for="email">Email address</label>
<input type="email" id="email" name="email" required autocomplete="email">
<button type="submit">Subscribe</button>
</form>
Client-side validation with JavaScript is a nice extra. Server-side validation is mandatory. HTML5 attributes like required, type="email", and pattern give you free baseline checks in modern browsers without a framework.
Navigation and links
Tabbed interfaces built as empty <div> containers with click handlers fail without JS. A progressive approach uses real links to real sections, then optionally upgrades:
<nav aria-label="Course sections">
<ul>
<li><a href="#planning">Planning structure</a></li>
<li><a href="#landmarks">Landmarks</a></li>
<li><a href="#forms">Forms in depth</a></li>
</ul>
</nav>
<section id="planning">
<h2>Planning structure</h2>
<!-- content -->
</section>
<section id="landmarks">
<h2>Landmarks</h2>
<!-- content -->
</section>
With no JavaScript, the user follows in-page anchors and reads each section. With JavaScript, you might hide sections and swap visibility – but the URLs and headings still make sense.
Native HTML for disclosure
Before reaching for an accordion plugin, consider <details> and <summary>:
<details>
<summary>What is progressive enhancement?</summary>
<p>It means starting with HTML that works everywhere, then adding CSS and JavaScript for browsers that support them.</p>
</details>
<details>
<summary>Do I need JavaScript at all?</summary>
<p>Often yes - for live search, maps, or rich editors. The point is not to ban JS but to avoid depending on it for basic access to content.</p>
</details>
They are keyboard accessible, work without custom ARIA, and degrade gracefully. Style them with CSS; add JS only if you need accordion behaviour where only one panel may be open at a time.
The <dialog> element
Modern browsers support native modal dialogs. A form inside a dialog can still submit to the server if you skip the fancy client-only flow:
<button type="button" onclick="helpDialog.showModal()">Get help</button>
<dialog id="helpDialog">
<form method="dialog">
<p>Email support@example.com or use the form on the contact page.</p>
<button type="submit">Close</button>
</form>
</dialog>
Provide a non-dialog path too – the same help text on a regular page – for browsers or scenarios where modals are awkward.
The <noscript> element
<noscript> holds content shown when JavaScript is disabled. Use it sparingly for genuine alternatives, not passive-aggressive scolding:
<noscript>
<p>The interactive code editor needs JavaScript.
<a href="/downloads/exercise.zip">Download the exercise files</a> to work offline instead.</p>
</noscript>
Most users have JavaScript enabled. <noscript> matters for locked-down environments, certain assistive setups, and the occasional network failure that stops your bundle loading.
Enhancement vs replacement
Enhancement adds to something that already works:
- Instant field validation after blur.
- Lazy-loading images below the fold.
- Fetching related posts without a full page reload.
Replacement removes the baseline:
- Pagination that only exists as JSON fetched into an empty container.
- A ‘Submit’ button that prevents default and never posts if the fetch fails.
- Content rendered only after a client router initialises.
Replacement is sometimes justified. For tutorial sites, marketing pages, and documentation, enhancement is the safer default.
CSS is part of the story too
Progressive enhancement applies to CSS. Layout should not collapse entirely when a stylesheet fails to load. Semantic HTML – headings, lists, landmarks – gives you a readable outline even unstyled. Avoid hiding critical content off-screen in ways that only CSS reveals; screen reader and no-CSS users may miss it.
Respect user preferences baked into CSS: prefers-reduced-motion, prefers-color-scheme, and logical focus styles with :focus-visible. They are enhancements that meet people where they already are.
Testing the baseline
Try these before you call a page done:
- Disable JavaScript in DevTools and click through the main tasks.
- Throttle the network and check whether content appears before your bundle loads.
- Tab through interactive elements with the keyboard only.
- Read the page with a screen reader – does the order match the visual story?
Progressive enhancement is a discipline, not a single attribute. Build the HTML so the page still makes sense on its own, then add polish where it genuinely helps. Your future self – and anyone on a slow connection – will be glad you did.

