C.W.K.
Stream
Lesson 02 of 03 · published

The JavaScript Bridge: Where Behavior Begins

~10 min · javascript, behavior, progressive-enhancement

Level 0Markup Novice
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"CSS can do so much more than it used to. JavaScript starts where computation, network, storage, and dynamic state begin."

The Modern Boundary

The CSS/JavaScript line has moved dramatically toward CSS over the past five years. Things that required JS in 2020 are now pure CSS in 2026:

  • Theme toggling — prefers-color-scheme + custom properties.
  • Show/hide based on validation — :has(:invalid).
  • Sticky headers — position: sticky.
  • Smooth scroll — scroll-behavior: smooth.
  • Responsive container adaptation — container queries.
  • 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.
  • Scheduling. Timeouts, intervals, debouncing input, polling.
  • 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.

  1. Server-rendered HTML — the page renders correctly even if JS fails to load (slow network, ad blocker, JS error).
  2. Semantic, accessible markup — works in screen readers, search engines, reader modes.
  3. CSS for presentation and reactive state — looks right, responds to user preferences.
  4. 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>&lt;div&gt;</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>

External links

Exercise

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.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.