JavaScript

Interactive & engaging effects

Five patterns you'll use on almost every site — toggles, tabs, accordions, live form feedback, and scroll-reveal animations. Each is just a few lines of JavaScript. Try them, then copy the code.

Pattern 1

Toggle — show & hide

The simplest, most-used trick: flip something on and off. It powers menus, dropdowns, "read more", dark mode, and modals. The whole secret is classList.toggle().

Live — click to toggle
<button class="btn" id="toggleBtn">Show details</button>
<div class="panel hidden" id="togglePanel">Surprise! 🎉 This panel was hidden until you clicked. Toggling a single class did all the work.</div>
const btn   = document.querySelector("#toggleBtn");
const panel = document.querySelector("#togglePanel");

btn.addEventListener("click", () => {
  panel.classList.toggle("hidden");   // .hidden { display:none } in CSS
  btn.textContent = panel.classList.contains("hidden")
    ? "Show details" : "Hide details";
});
.btn {
  padding: 10px 18px;
  border-radius: 8px;
  border: none;
  cursor: pointer;
  font: inherit;
  font-weight: 700;
  background: linear-gradient(135deg,#6366f1,#ec4899);
  color: #fff;
}
.panel {
  margin-top: 14px;
  padding: 18px;
  border-radius: 10px;
  background: #16161c;
  border: 1px solid rgba(255,255,255,.1);
  color: #cbd5e1;
}
.hidden { display: none; }
Pattern 2

Tabs

Pack lots of content into one space. Click a tab → show its panel, hide the rest. Great for product details, settings, and dashboards.

Live — click the tabs
A quick summary lives here. Tabs keep related content tidy.
Bullet-proof feature list goes here — fast, simple, accessible.
★★★★★ "Couldn't be easier." — a happy user.
<div class="tabs" id="tabs">
  <button class="tab active" data-tab="0">Overview</button>
  <button class="tab" data-tab="1">Features</button>
  <button class="tab" data-tab="2">Reviews</button>
</div>
<div class="tab-panel active" data-panel="0">A quick summary lives here. Tabs keep related content tidy.</div>
<div class="tab-panel" data-panel="1">Bullet-proof feature list goes here — fast, simple, accessible.</div>
<div class="tab-panel" data-panel="2">★★★★★ "Couldn't be easier." — a happy user.</div>
const tabs   = document.querySelectorAll(".tab");
const panels = document.querySelectorAll(".tab-panel");

tabs.forEach((tab) => {
  tab.addEventListener("click", () => {
    // clear all, then activate the clicked one
    tabs.forEach(t => t.classList.remove("active"));
    panels.forEach(p => p.classList.remove("active"));
    tab.classList.add("active");
    panels[tab.dataset.tab].classList.add("active");
  });
});
.tabs { display: flex; gap: 6px; border-bottom: 1px solid rgba(255,255,255,.1); }
.tab { padding: 10px 16px; background: none; border: none; color: #a1a1aa; font: inherit; font-weight: 600; cursor: pointer; border-bottom: 2px solid transparent; }
.tab.active { color: #fff; border-bottom-color: #ec4899; }
.tab-panel { display: none; padding: 16px 4px; color: #cbd5e1; }
.tab-panel.active { display: block; }
Pattern 3

Accordion / FAQ

Expandable rows — perfect for FAQs. Click a header to open its answer and smoothly push the others down.

Live — click a question

Not if you take it one piece at a time — start with the DOM and events, like the page before this one.

No. Plain "vanilla" JavaScript handles everything on this page. Frameworks come later, if at all.

Right in the browser. Add a <script> tag and open your page — that's it.

<div id="acc">
  <div class="acc-item">
    <button class="acc-head">Is JavaScript hard to learn? <i class="ph-bold ph-caret-down chev"></i></button>
    <div class="acc-body"><p>Not if you take it one piece at a time — start with the DOM and events, like the page before this one.</p></div>
  </div>
  <div class="acc-item">
    <button class="acc-head">Do I need a framework? <i class="ph-bold ph-caret-down chev"></i></button>
    <div class="acc-body"><p>No. Plain "vanilla" JavaScript handles everything on this page. Frameworks come later, if at all.</p></div>
  </div>
  <div class="acc-item">
    <button class="acc-head">Where do I run it? <i class="ph-bold ph-caret-down chev"></i></button>
    <div class="acc-body"><p>Right in the browser. Add a <code>&lt;script&gt;</code> tag and open your page — that's it.</p></div>
  </div>
</div>
document.querySelectorAll(".acc-head").forEach((head) => {
  head.addEventListener("click", () => {
    const item = head.parentElement;
    const body = head.nextElementSibling;
    item.classList.toggle("open");
    // animate height: 0 ↔ content height
    body.style.maxHeight = item.classList.contains("open")
      ? body.scrollHeight + "px" : null;
  });
});
.acc-item { border: 1px solid rgba(255,255,255,.1); border-radius: 10px; margin-bottom: 8px; overflow: hidden; }
.acc-head {
  width: 100%;
  text-align: left;
  padding: 14px 16px;
  background: #16161c;
  border: none;
  color: #f5f5f7;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.acc-head .chev { transition: transform .2s; color: #818cf8; }
.acc-item.open .chev { transform: rotate(180deg); }
.acc-body { max-height: 0; overflow: hidden; transition: max-height .25s ease; background: #101015; }
.acc-body p { padding: 14px 16px; color: #a1a1aa; font-size: 0.9rem; }
Pattern 4

Live form feedback

Instant feedback makes forms feel alive. Here a character counter updates as you type and warns you as you near the limit — pure input event.

Live — type in the box
0 / 120
<textarea class="field" id="bio" maxlength="120" placeholder="Write a short bio (max 120 chars)…"></textarea>
<div class="counter" id="counter">0 / 120</div>
const box     = document.querySelector("#bio");
const counter = document.querySelector("#counter");
const max = 120;

box.addEventListener("input", () => {
  const len = box.value.length;
  counter.textContent = len + " / " + max;
  counter.classList.toggle("warn", len > max * 0.8);  // turn amber near limit
});
.field {
  width: 100%;
  padding: 12px;
  border-radius: 8px;
  background: #0d0d12;
  border: 1px solid rgba(255,255,255,.14);
  color: #f5f5f7;
  font: inherit;
  resize: vertical;
  min-height: 80px;
}
.counter { margin-top: 8px; font-size: 0.85rem; color: #a1a1aa; text-align: right; }
.counter.warn { color: #f59e0b; }
.counter.over { color: #ef4444; }
Pattern 5

Scroll reveal

Fade elements in as they scroll into view — the effect that makes modern sites feel polished. The modern, performant way is IntersectionObserver (no scroll-event spam). The cards below animate in as they enter the screen.

Live — scroll so these enter view

Fast

Light

Smooth

Engaging

<div class="reveal-grid">
  <div class="reveal"><i class="ph-duotone ph-rocket"></i><h4>Fast</h4></div>
  <div class="reveal"><i class="ph-duotone ph-feather"></i><h4>Light</h4></div>
  <div class="reveal"><i class="ph-duotone ph-sparkle"></i><h4>Smooth</h4></div>
  <div class="reveal"><i class="ph-duotone ph-heart"></i><h4>Engaging</h4></div>
</div>
const items = document.querySelectorAll(".reveal");

const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      entry.target.classList.add("visible");   // fade it in
    }
  });
}, { threshold: 0.2 });

items.forEach(item => observer.observe(item));
.reveal-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
}
.reveal {
  background: #16161c;
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 12px;
  padding: 24px;
  text-align: center;
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .5s ease, transform .5s ease;
}
.reveal.visible { opacity: 1; transform: none; }
.reveal i { font-size: 1.8rem; color: #818cf8; }
.reveal h4 { margin-top: 8px; font-size: 0.95rem; }
That's the toolkit. These five patterns cover most interactivity you'll ever need. Pair them with DOM & Events and the component labs (Modal, Tabs, Accordion) to go further.