Native dialog open/close — <dialog> + showModal()/close() (the open/close itself is one line of JS, but state, focus trap, and Esc-to-close are native).
Form validation — HTML5 attributes + :invalid.
What's still JavaScript's job in 2026:
State that doesn't surface as a DOM attribute. User's shopping cart, the current page in a SPA, the selected items in a multi-select. State has to live somewhere; usually in JS.
Network requests. Fetching data, submitting forms via AJAX, WebSockets, Server-Sent Events.
Computation. Sorting a list, filtering a table, parsing JSON, computing summaries, running algorithms.
Storage.localStorage, IndexedDB, cookies. Reading/writing user data.
Generating content on the fly. Creating DOM nodes from data, rendering React/Vue/Svelte components.
OS/browser API integration. Geolocation, notifications, clipboard, file system.
Animations driven by physics or scroll position. Spring animations, scrubbing animations with the Web Animations API or third-party libraries.
Progressive Enhancement: Start With What Works Without JS
The web's most resilient design pattern: build pages that work with zero JavaScript, then enhance with JS where it adds value.
Server-rendered HTML — the page renders correctly even if JS fails to load (slow network, ad blocker, JS error).
Semantic, accessible markup — works in screen readers, search engines, reader modes.
CSS for presentation and reactive state — looks right, responds to user preferences.
JavaScript adds dynamism — instant feedback, no full page reloads, rich interactions.
Modern frameworks (Next.js, Remix, SvelteKit) make progressive enhancement easy: server-render the markup, hydrate it client-side, but everything works at every stage.
The Minimal-JS Discipline
For every interaction, ask: can CSS do it? Often the answer is yes:
A dropdown that opens on click → <details>/<summary>, native HTML, zero JS.
A tab control → CSS-only with radio buttons and :checked + sibling selectors. (Better with JS for accessibility, but CSS-only works.)
A modal → native <dialog> element, one line of JS to showModal().
An accordion → <details> with custom styling.
Smooth scroll to anchor → scroll-behavior: smooth + an <a href="#section">.
Less JS = faster page, fewer bugs, more accessible. The principle isn't "never JavaScript"; it's "use the simplest layer that solves the problem."
When You DO Need JavaScript, What Should You Reach For?
For most apps in 2026:
Vanilla JS for small interactions (clipboard copy, theme toggle, form helpers).
React for medium-to-large interactive UIs (the dominant choice; React Quest covers this).
Next.js for full-stack web apps with routing, data fetching, and SSR (Next.js Quest).
Web Components for framework-agnostic reusable components.
Svelte / Vue / Solid as React alternatives — each has trade-offs; React's ecosystem is the most universal.
HTML for structure, CSS for presentation, JavaScript for behavior. The boundaries blur — modern CSS has reactive features, HTML has form validation — but the principle holds. Reach for the simplest layer that does the job. JavaScript wins on capability; HTML/CSS wins on resilience.
Pippa's Note
Pippa's WebUI is a React app, but a surprising amount of the UI is HTML + CSS. The chat message rendering is React (data → JSX → DOM), but every message bubble is a semantic <article> with HTML5 elements inside. The theme toggle is one line of JS that sets data-theme on <html> — every visual change is CSS reading var(). The brain selector menu is a custom dropdown (because we need keyboard nav and search), but the avatar emotion picker is just <details>/<summary> with no JS. The right tool, layer by layer.
Code
HTML elements that used to need libraries·html
<!-- Native disclosure: <details> for an accordion, zero JS -->
<details>
<summary>What's in Track 1?</summary>
<p>HTML foundations — semantic markup, document structure,
text elements, links and images, and why <code><div></code>
is the last resort.</p>
</details>
<!-- Native dialog with one line of JS to open -->
<button onclick="document.getElementById('confirm').showModal()">
Delete account
</button>
<dialog id="confirm">
<p>Are you sure? This can't be undone.</p>
<form method="dialog">
<button value="cancel">Cancel</button>
<button value="confirm" autofocus>Delete</button>
</form>
</dialog>
Where JS earns its keep·javascript
// Where JavaScript shines: state, network, computation
// 1. Theme toggle — minimum JS, maximum CSS
function toggleTheme() {
const isDark = document.documentElement.dataset.theme === 'dark';
document.documentElement.dataset.theme = isDark ? 'light' : 'dark';
localStorage.setItem('theme', isDark ? 'light' : 'dark');
}
// All the visual change happens via CSS custom properties
// inheriting from [data-theme="dark"] / [data-theme="light"].
// 2. Fetch + render data
async function loadConversations() {
const res = await fetch('/api/conversations');
const conversations = await res.json();
document.getElementById('list').innerHTML = conversations
.map(c => `<li><a href="/c/${c.id}">${c.title}</a></li>`)
.join('');
}
// 3. Sorting / filtering
function sortByDate(items) {
return [...items].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
}
// 4. Storage / preference
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.dataset.theme = saved;
Progressive enhancement in 12 lines·html
<!-- Progressive enhancement — works without JS, better with JS -->
<!-- The form works without JS: it does a full page navigation. -->
<form action="/api/search" method="get">
<label for="q">Search</label>
<input id="q" name="q" type="search" required />
<button type="submit">Search</button>
</form>
<script>
// JS enhancement: intercept and use fetch for instant results
document.querySelector('form').addEventListener('submit', async (e) => {
e.preventDefault();
const q = new FormData(e.target).get('q');
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
const html = await res.text();
document.getElementById('results').innerHTML = html;
});
</script>
<div id="results"></div>
Take an existing page that uses JavaScript and identify three behaviors that could be HTML/CSS instead: a custom accordion (→ <details>), a custom modal (→ <dialog>), a custom theme toggle (→ CSS custom properties + one line of JS). Replace each. Measure the JavaScript line-count reduction. Confirm the user-facing behavior is identical.
Hint
If something feels too complex to convert, that's often the right signal — keep it as JS. The goal isn't 'no JavaScript'; it's 'minimum JavaScript for the desired behavior.' For accordions, modals, theme toggles, smooth scroll: the platform handles it.
Progress
Progress is local-only — sign in to sync across devices.