Skip to content

Instantly share code, notes, and snippets.

@dragontheory
Last active January 19, 2026 17:18
Show Gist options
  • Select an option

  • Save dragontheory/1fb51473a3e010f32fd50615fd65df09 to your computer and use it in GitHub Desktop.

Select an option

Save dragontheory/1fb51473a3e010f32fd50615fd65df09 to your computer and use it in GitHub Desktop.
Modern HTML + CSS patterns that replace or drastically reduce traditional JavaScript UI behavior.

Image

Modern HTML + CSS patterns that replace or drastically reduce traditional JavaScript UI behavior. Each example is progressive, accessible, minimal, and declarative.


1) Toggle / Accordion → <details> + <summary>

Replaces: JS click handlers, state variables

<details>
  <summary>More info</summary>
  <p>Native toggle state, keyboard + screen‑reader support.</p>
</details>

Why it works

  • Built‑in open/close state
  • Keyboard + ARIA handled by the browser
  • Style with details[open] { … }

2) Modal / Dialog → <dialog> (no JS required)

Replaces: modal JS frameworks

<dialog open>
  <form method="dialog">
    <p>Native modal</p>
    <button>Close</button>
  </form>
</dialog>

Why it works

  • Native focus trapping
  • Escape key handling
  • Semantic intent

3) Conditional UI Logic → :has()

Replaces: JS DOM traversal & state checks

form:has(input:invalid) button {
  opacity: .4;
  pointer-events: none;
}
<form>
  <input required>
  <button>Submit</button>
</form>

Why it works

  • Parent reacts to child state
  • Eliminates JS “watchers”
  • Enables real UI logic in CSS

4) Tabs → Radio Buttons + :checked

Replaces: JS tab controllers

<input type="radio" name="tab" checked>
<section>Tab A</section>

<input type="radio" name="tab">
<section>Tab B</section>
input { display: none; }
input + section { display: none; }
input:checked + section { display: block; }

Why it works

  • Real state
  • Keyboard accessible
  • No JS state machine

5) UI Responsiveness → Container Queries

Replaces: JS resize listeners

.card {
  container-type: inline-size;
}

@container (min-width: 40rem) {
  .card { grid-template-columns: 1fr 2fr; }
}

Why it works

  • Layout reacts to component size, not viewport
  • No JS observers
  • True component isolation

6) Theme Switching → prefers-color-scheme

Replaces: JS theme toggles

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    background: #111;
  }
}

Why it works

  • Zero JS
  • User preference respected
  • Automatic OS sync

7) Scroll Animations → Scroll‑Driven Animations

Replaces: JS scroll listeners

@scroll-timeline reveal {
  source: auto;
}

.fade {
  animation: appear 1s linear;
  animation-timeline: reveal;
}

@keyframes appear {
  from { opacity: 0; }
  to { opacity: 1; }
}

Why it works

  • Declarative animation
  • GPU‑optimized
  • No JS throttling

8) Form Validation & Messaging → Native HTML

Replaces: JS validation libraries

<input type="email" required>
<span class="error">Invalid email</span>
input:valid + .error { display: none; }

Why it works

  • Browser handles validation
  • CSS reflects validity state
  • Zero custom logic

9) Feature Detection → @supports

Replaces: JS capability checks

@supports (container-type: inline-size) {
  .layout { container-type: inline-size; }
}

Why it works

  • Declarative
  • Predictable
  • No runtime branching

Key Architectural Takeaways

  • HTML = state
  • CSS = logic
  • JS = data only
  • Prefer native semantics over abstractions
  • If the browser already knows how to do it—don’t re‑implement it

This stack scales cleaner, faster, and more accessibly than JS‑first UI patterns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment