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().
<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; }
Tabs
Pack lots of content into one space. Click a tab → show its panel, hide the rest. Great for product details, settings, and dashboards.
<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; }
Accordion / FAQ
Expandable rows — perfect for FAQs. Click a header to open its answer and smoothly push the others down.
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><script></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; }
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.
<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; }
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.
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; }