/ webflow magic

150 Attribute Tricks
for Webflow

Drop any of these into Webflow via Custom Attributes or an Embed block. No plugins. No page builders. Just pure CSS + JS.

CSS Only JS Only CSS + JS

No tricks found. Try a different search.

#01 CSS Only

Fade In on Scroll

Elements fade in as they enter the viewport. No JS needed — pure CSS scroll-driven animation.

Attribute
data-animate="fade-in"
CSS
[data-animate="fade-in"] {
  opacity: 0;
  animation: fadeIn linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 25%;
}
@keyframes fadeIn { to { opacity: 1; } }
Animations
#02 CSS Only

Slide Up on Scroll

Elements slide up into view on scroll entry. Great for cards, headings, and sections.

Attribute
data-animate="slide-up"
CSS
[data-animate="slide-up"] {
  opacity: 0;
  transform: translateY(40px);
  animation: slideUp linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 30%;
}
@keyframes slideUp { to { opacity: 1; transform: none; } }
Animations
#03 CSS + JS

Stagger Children

Automatically staggers the animation delay for each direct child element.

Attribute
data-stagger="children"
CSS
[data-stagger="children"] > * {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
[data-stagger="children"].is-visible > * { opacity: 1; transform: none; }
[data-stagger="children"].is-visible > *:nth-child(1) { transition-delay: 0ms; }
[data-stagger="children"].is-visible > *:nth-child(2) { transition-delay: 80ms; }
[data-stagger="children"].is-visible > *:nth-child(3) { transition-delay: 160ms; }
[data-stagger="children"].is-visible > *:nth-child(4) { transition-delay: 240ms; }
[data-stagger="children"].is-visible > *:nth-child(5) { transition-delay: 320ms; }
JS JavaScript
document.querySelectorAll('[data-stagger="children"]').forEach(el => {
  new IntersectionObserver(([e]) => {
    if (e.isIntersecting) el.classList.add('is-visible');
  }, { threshold: 0.1 }).observe(el);
});
Animations
#04 JS Only

Parallax Scroll

Moves an element at a different speed than the scroll. Set speed with data-parallax='0.3'.

Attribute
data-parallax="0.3"
JS JavaScript
document.querySelectorAll('[data-parallax]').forEach(el => {
  const speed = parseFloat(el.dataset.parallax) || 0.3;
  window.addEventListener('scroll', () => {
    el.style.transform = `translateY(${window.scrollY * speed}px)`;
  }, { passive: true });
});
Animations
#05 JS Only

Count Up Number

Animates a number from 0 to its text value when it enters the viewport.

Attribute
data-countup
JS JavaScript
document.querySelectorAll('[data-countup]').forEach(el => {
  const target = parseInt(el.textContent, 10);
  const duration = 1500;
  let start = null;
  const observer = new IntersectionObserver(([e]) => {
    if (!e.isIntersecting) return;
    observer.disconnect();
    const step = (ts) => {
      if (!start) start = ts;
      const progress = Math.min((ts - start) / duration, 1);
      el.textContent = Math.floor(progress * target);
      if (progress < 1) requestAnimationFrame(step);
      else el.textContent = target;
    };
    requestAnimationFrame(step);
  });
  observer.observe(el);
});
Animations
#06 CSS + JS

Typewriter Text

Types out text character by character. Set the text in the attribute itself.

Attribute
data-typewriter="Hello, World!"
CSS
[data-typewriter]::after {
  content: '|';
  animation: blink 0.7s step-end infinite;
}
@keyframes blink { 50% { opacity: 0; } }
JS JavaScript
document.querySelectorAll('[data-typewriter]').forEach(el => {
  const text = el.dataset.typewriter;
  el.textContent = '';
  let i = 0;
  const type = () => {
    if (i < text.length) { el.textContent += text[i++]; setTimeout(type, 55); }
  };
  new IntersectionObserver(([e]) => {
    if (e.isIntersecting) { type(); }
  }, { threshold: 0.5 }).observe(el);
});
Animations
#07 CSS Only

Pulse Glow

Adds a repeating glow pulse. Great for CTAs and live status indicators.

Attribute
data-glow="pulse"
CSS
[data-glow="pulse"] {
  animation: glowPulse 2s ease-in-out infinite;
}
@keyframes glowPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(99,102,241,0.5); }
  50%       { box-shadow: 0 0 0 12px rgba(99,102,241,0); }
}
Animations
#08 CSS Only

Shake on Hover

Shakes an element on hover — useful for error states or attention-grabbing elements.

Attribute
data-hover="shake"
CSS
[data-hover="shake"]:hover {
  animation: shake 0.4s ease;
}
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20%       { transform: translateX(-6px); }
  40%       { transform: translateX(6px); }
  60%       { transform: translateX(-4px); }
  80%       { transform: translateX(4px); }
}
Animations
#09 CSS + JS

Sticky Progress Bar

A reading progress bar fixed to the top of the page that fills as you scroll.

Attribute
data-progress-bar
CSS
[data-progress-bar] {
  position: fixed;
  top: 0; left: 0;
  height: 3px;
  width: 0%;
  background: var(--accent, #6366f1);
  z-index: 9999;
  transition: width 0.1s linear;
}
JS JavaScript
const bar = document.querySelector('[data-progress-bar]');
if (bar) {
  window.addEventListener('scroll', () => {
    const pct = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
    bar.style.width = pct + '%';
  }, { passive: true });
}
Scroll
#10 CSS + JS

Scroll to Target

Smooth scrolls to the element with the matching ID. Add to any link or button.

Attribute
data-scroll-to="#section-id"
CSS
html { scroll-behavior: smooth; }
JS JavaScript
document.querySelectorAll('[data-scroll-to]').forEach(el => {
  el.addEventListener('click', e => {
    e.preventDefault();
    document.querySelector(el.dataset.scrollTo)?.scrollIntoView({ behavior: 'smooth' });
  });
});
Scroll
#11 CSS Only

Scroll Snap Section

Turns a wrapper into a full-screen scroll-snapping container — each child snaps into view.

Attribute
data-snap="container"
CSS
[data-snap="container"] {
  height: 100vh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
}
[data-snap="container"] > * {
  scroll-snap-align: start;
  min-height: 100vh;
}
Scroll
#12 CSS + JS

Back to Top Button

Shows a back-to-top button after scrolling 400px, hides it at the top.

Attribute
data-back-to-top
CSS
[data-back-to-top] {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
[data-back-to-top].is-visible {
  opacity: 1;
  pointer-events: auto;
}
JS JavaScript
const btn = document.querySelector('[data-back-to-top]');
if (btn) {
  window.addEventListener('scroll', () => {
    btn.classList.toggle('is-visible', window.scrollY > 400);
  }, { passive: true });
  btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
}
Scroll
#13 CSS Only

Horizontal Scroll Section

Makes a section scroll horizontally. Add to a wide container with child columns.

Attribute
data-scroll="horizontal"
CSS
[data-scroll="horizontal"] {
  overflow-x: auto;
  overflow-y: hidden;
  display: flex;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
[data-scroll="horizontal"]::-webkit-scrollbar { display: none; }
[data-scroll="horizontal"] > * {
  flex: 0 0 auto;
  scroll-snap-align: start;
}
Scroll
#14 CSS + JS

Custom Cursor Follower

A custom circle that follows the cursor with a smooth lag effect.

Attribute
data-cursor="follow"
CSS
[data-cursor="follow"] {
  position: fixed;
  width: 20px; height: 20px;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;
  transition: transform 0.12s ease, opacity 0.3s ease;
}
JS JavaScript
const cursor = document.querySelector('[data-cursor="follow"]');
if (cursor) {
  let mx = 0, my = 0, cx = 0, cy = 0;
  document.addEventListener('mousemove', e => { mx = e.clientX; my = e.clientY; });
  (function loop() {
    cx += (mx - cx) * 0.12;
    cy += (my - cy) * 0.12;
    cursor.style.left = (cx - 10) + 'px';
    cursor.style.top  = (cy - 10) + 'px';
    requestAnimationFrame(loop);
  })();
}
Cursor
#15 CSS + JS

Magnetic Button

Button pulls toward the cursor like a magnet on hover. Perfect for CTAs.

Attribute
data-magnetic
CSS
[data-magnetic] { transition: transform 0.3s ease; }
JS JavaScript
document.querySelectorAll('[data-magnetic]').forEach(el => {
  el.addEventListener('mousemove', e => {
    const r = el.getBoundingClientRect();
    const x = (e.clientX - r.left - r.width / 2) * 0.35;
    const y = (e.clientY - r.top - r.height / 2) * 0.35;
    el.style.transform = `translate(${x}px, ${y}px)`;
  });
  el.addEventListener('mouseleave', () => el.style.transform = '');
});
Cursor
#16 CSS + JS

Cursor Blend Mode

Cursor element uses mix-blend-mode: difference for an invert effect over any content.

Attribute
data-cursor="blend"
CSS
[data-cursor="blend"] {
  position: fixed;
  width: 24px; height: 24px;
  background: #fff;
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;
  mix-blend-mode: difference;
}
JS JavaScript
const cur = document.querySelector('[data-cursor="blend"]');
if (cur) {
  document.addEventListener('mousemove', e => {
    cur.style.left = (e.clientX - 12) + 'px';
    cur.style.top  = (e.clientY - 12) + 'px';
  });
}
Cursor
#17 CSS Only

Gradient Text

Applies an animated gradient to text using background-clip trick.

Attribute
data-text="gradient"
CSS
[data-text="gradient"] {
  background: linear-gradient(90deg, #6366f1, #ec4899, #f59e0b);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
Text
#18 CSS Only

Animated Gradient Text

Same as gradient text but the gradient shifts continuously — eye-catching hero headings.

Attribute
data-text="gradient-animate"
CSS
[data-text="gradient-animate"] {
  background: linear-gradient(90deg, #6366f1, #ec4899, #f59e0b, #6366f1);
  background-size: 200% auto;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  animation: gradShift 3s linear infinite;
}
@keyframes gradShift { to { background-position: 200% center; } }
Text
#19 JS Only

Scramble Text on Hover

Scrambles the text with random characters on hover, then resolves back.

Attribute
data-scramble
JS JavaScript
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%';
document.querySelectorAll('[data-scramble]').forEach(el => {
  const original = el.textContent;
  let frame, iteration = 0;
  el.addEventListener('mouseenter', () => {
    clearInterval(frame);
    iteration = 0;
    frame = setInterval(() => {
      el.textContent = original.split('').map((c, i) => {
        if (c === ' ') return ' ';
        if (i < iteration) return original[i];
        return chars[Math.floor(Math.random() * chars.length)];
      }).join('');
      if (iteration >= original.length) clearInterval(frame);
      iteration += 0.5;
    }, 30);
  });
});
Text
#20 CSS + JS

Split Word Reveal

Splits each word into a span and reveals them with a staggered animation.

Attribute
data-reveal="words"
CSS
[data-reveal="words"] .word {
  display: inline-block;
  opacity: 0;
  transform: translateY(100%);
  transition: opacity 0.4s ease, transform 0.4s ease;
}
[data-reveal="words"].is-visible .word { opacity: 1; transform: none; }
JS JavaScript
document.querySelectorAll('[data-reveal="words"]').forEach(el => {
  el.innerHTML = el.textContent.split(' ')
    .map((w, i) => `<span class="word" style="transition-delay:${i * 60}ms">${w}&nbsp;</span>`)
    .join('');
  new IntersectionObserver(([e]) => {
    if (e.isIntersecting) el.classList.add('is-visible');
  }, { threshold: 0.3 }).observe(el);
});
Text
#21 CSS Only

Text Stroke Outline

Makes text outline-only with no fill — great for large display headings.

Attribute
data-text="outline"
CSS
[data-text="outline"] {
  -webkit-text-stroke: 1.5px currentColor;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
Text
#22 CSS + JS

Highlight on Scroll

Highlights a span of text like a marker pen when it scrolls into view.

Attribute
data-highlight
CSS
[data-highlight] {
  background: linear-gradient(to right, #fde047 0%, #fde047 100%) no-repeat;
  background-size: 0% 40%;
  background-position: 0 80%;
  transition: background-size 0.6s ease;
}
[data-highlight].is-visible { background-size: 100% 40%; }
JS JavaScript
document.querySelectorAll('[data-highlight]').forEach(el => {
  new IntersectionObserver(([e]) => {
    if (e.isIntersecting) el.classList.add('is-visible');
  }, { threshold: 0.6 }).observe(el);
});
Text
#23 CSS + JS

Toggle Visibility

Clicking a trigger toggles the target element's visibility. Accordion-style.

Attribute
data-toggle="#target-id"
CSS
[data-toggle-target] { display: none; }
[data-toggle-target].is-open { display: block; }
JS JavaScript
document.querySelectorAll('[data-toggle]').forEach(trigger => {
  trigger.addEventListener('click', () => {
    document.querySelectorAll(trigger.dataset.toggle).forEach(target => {
      target.classList.toggle('is-open');
    });
  });
});
UI Patterns
#24 CSS + JS

Accordion

Native-feeling accordion with smooth height transition using the details element pattern.

Attribute
data-accordion
CSS
[data-accordion] { overflow: hidden; max-height: 0; transition: max-height 0.4s ease; }
[data-accordion].is-open { max-height: 600px; }
JS JavaScript
document.querySelectorAll('[data-accordion-trigger]').forEach(trigger => {
  trigger.addEventListener('click', () => {
    const target = document.querySelector(trigger.dataset.accordionTrigger);
    target?.classList.toggle('is-open');
    trigger.setAttribute('aria-expanded', target?.classList.contains('is-open') ? 'true' : 'false');
  });
});
UI Patterns
#25 CSS Only

Tooltip on Hover

Pure CSS tooltip — no JS. The content comes from the attribute value.

Attribute
data-tooltip="Your tooltip text"
CSS
[data-tooltip] { position: relative; cursor: default; }
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: #111;
  color: #fff;
  font-size: 0.75rem;
  padding: 0.3rem 0.7rem;
  border-radius: 6px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}
[data-tooltip]:hover::after { opacity: 1; }
UI Patterns
#26 CSS + JS

Copy to Clipboard

Copies the element's text content to clipboard on click and shows a confirmation.

Attribute
data-copy
CSS
[data-copy] { cursor: pointer; }
[data-copy].copied::after { content: ' ✓ Copied'; color: #6bcb77; font-size: 0.8em; }
JS JavaScript
document.querySelectorAll('[data-copy]').forEach(el => {
  el.addEventListener('click', () => {
    navigator.clipboard.writeText(el.textContent.trim()).then(() => {
      el.classList.add('copied');
      setTimeout(() => el.classList.remove('copied'), 2000);
    });
  });
});
UI Patterns
#27 CSS + JS

Tab Switcher

Simple tab component. Trigger has data-tab='name', content has data-tab-panel='name'.

Attribute
data-tab="panel-name"
CSS
[data-tab-panel] { display: none; }
[data-tab-panel].is-active { display: block; }
[data-tab].is-active { font-weight: 700; border-bottom: 2px solid currentColor; }
JS JavaScript
document.querySelectorAll('[data-tab]').forEach(tab => {
  tab.addEventListener('click', () => {
    document.querySelectorAll('[data-tab]').forEach(t => t.classList.remove('is-active'));
    document.querySelectorAll('[data-tab-panel]').forEach(p => p.classList.remove('is-active'));
    tab.classList.add('is-active');
    document.querySelector(`[data-tab-panel="${tab.dataset.tab}"]`)?.classList.add('is-active');
  });
});
UI Patterns
#28 CSS + JS

Modal Open / Close

Opens a modal overlay. data-modal-open targets the modal ID, data-modal-close is inside it.

Attribute
data-modal-open="#modal-id"
CSS
[data-modal] {
  display: none;
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(4px);
  z-index: 9999;
  align-items: center;
  justify-content: center;
}
[data-modal].is-open { display: flex; }
JS JavaScript
document.querySelectorAll('[data-modal-open]').forEach(btn => {
  btn.addEventListener('click', () => {
    document.querySelector(btn.dataset.modalOpen)?.classList.add('is-open');
  });
});
document.querySelectorAll('[data-modal-close]').forEach(btn => {
  btn.closest('[data-modal]')?.addEventListener('click', e => {
    if (e.target === btn || e.target === btn.closest('[data-modal]'))
      btn.closest('[data-modal]').classList.remove('is-open');
  });
});
UI Patterns
#29 CSS + JS

Lazy Load Image

Loads images only when they enter the viewport, using the src from a data attribute.

Attribute
data-src="/image.jpg"
CSS
[data-src] { opacity: 0; transition: opacity 0.4s ease; }
[data-src].loaded { opacity: 1; }
JS JavaScript
document.querySelectorAll('img[data-src]').forEach(img => {
  new IntersectionObserver(([e], obs) => {
    if (!e.isIntersecting) return;
    img.src = img.dataset.src;
    img.onload = () => img.classList.add('loaded');
    obs.disconnect();
  }).observe(img);
});
UI Patterns
#30 CSS + JS

Dark Mode Toggle

Toggles dark/light mode by adding a class to the <html> element and saves the preference.

Attribute
data-theme-toggle
CSS
html.dark { filter: invert(1) hue-rotate(180deg); }
html.dark img, html.dark video { filter: invert(1) hue-rotate(180deg); }
JS JavaScript
const toggle = document.querySelector('[data-theme-toggle]');
if (toggle) {
  const html = document.documentElement;
  if (localStorage.getItem('theme') === 'dark') html.classList.add('dark');
  toggle.addEventListener('click', () => {
    html.classList.toggle('dark');
    localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light');
  });
}
UI Patterns
#31 CSS Only

Equal Height Grid

Forces all children to equal height regardless of content length.

Attribute
data-grid="equal"
CSS
[data-grid="equal"] {
  display: grid;
  grid-auto-rows: 1fr;
  align-items: stretch;
}
[data-grid="equal"] > * { height: 100%; }
Layout
#32 CSS Only

Auto Masonry Grid

CSS-only masonry-style layout using columns property. No JS, no library.

Attribute
data-grid="masonry"
CSS
[data-grid="masonry"] {
  columns: 3 280px;
  column-gap: 1.5rem;
}
[data-grid="masonry"] > * {
  break-inside: avoid;
  margin-bottom: 1.5rem;
}
Layout
#33 CSS Only

Full Bleed Section

Makes an element inside a constrained container break out to full viewport width.

Attribute
data-bleed="full"
CSS
[data-bleed="full"] {
  width: 100vw;
  margin-left: calc(50% - 50vw);
}
Layout
#34 CSS Only

Aspect Ratio Box

Locks an element to a specific aspect ratio. Value like '16/9', '1/1', '4/3'.

Attribute
data-ratio="16/9"
CSS
[data-ratio] { aspect-ratio: attr(data-ratio); }
[data-ratio="16/9"] { aspect-ratio: 16/9; }
[data-ratio="1/1"]  { aspect-ratio: 1/1; }
[data-ratio="4/3"]  { aspect-ratio: 4/3; }
Layout
#35 CSS Only

Sticky Sidebar

Makes a sidebar column sticky while the main content scrolls past it.

Attribute
data-sticky
CSS
[data-sticky] {
  position: sticky;
  top: 2rem;
  align-self: start;
}
Layout
#36 CSS + JS

Tilt 3D Card

Card tilts in 3D following the mouse position for a depth effect on hover.

Attribute
data-tilt
CSS
[data-tilt] { transform-style: preserve-3d; transition: transform 0.1s ease; }
JS JavaScript
document.querySelectorAll('[data-tilt]').forEach(el => {
  el.addEventListener('mousemove', e => {
    const r = el.getBoundingClientRect();
    const x = (e.clientY - r.top - r.height / 2) / r.height * -20;
    const y = (e.clientX - r.left - r.width / 2) / r.width * 20;
    el.style.transform = `perspective(600px) rotateX(${x}deg) rotateY(${y}deg)`;
  });
  el.addEventListener('mouseleave', () => el.style.transform = '');
});
Visual FX
#37 CSS Only

Glass Morphism

Applies a frosted glass effect using backdrop-filter.

Attribute
data-glass
CSS
[data-glass] {
  background: rgba(255,255,255,0.1);
  backdrop-filter: blur(12px) saturate(180%);
  -webkit-backdrop-filter: blur(12px) saturate(180%);
  border: 1px solid rgba(255,255,255,0.18);
  border-radius: 12px;
}
Visual FX
#38 CSS Only

Noise Texture Overlay

Adds a subtle grain/noise texture over an element using SVG filter.

Attribute
data-noise
CSS
[data-noise] { position: relative; }
[data-noise]::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
  opacity: 0.06;
}
Visual FX
#39 CSS + JS

Spotlight Follow Mouse

Radial gradient spotlight on a card that follows the cursor, like a flashlight.

Attribute
data-spotlight
CSS
[data-spotlight] {
  position: relative;
  overflow: hidden;
}
[data-spotlight]::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%), rgba(255,255,255,0.08) 0%, transparent 60%);
  transition: opacity 0.3s;
  opacity: 0;
}
[data-spotlight]:hover::before { opacity: 1; }
JS JavaScript
document.querySelectorAll('[data-spotlight]').forEach(el => {
  el.addEventListener('mousemove', e => {
    const r = el.getBoundingClientRect();
    el.style.setProperty('--mx', ((e.clientX - r.left) / r.width * 100) + '%');
    el.style.setProperty('--my', ((e.clientY - r.top) / r.height * 100) + '%');
  });
});
Visual FX
#40 CSS Only

Gradient Border

Animated gradient border around any element using a pseudo-element trick.

Attribute
data-border="gradient"
CSS
[data-border="gradient"] {
  position: relative;
  border-radius: 12px;
  background: var(--bg, #fff);
  z-index: 0;
}
[data-border="gradient"]::before {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: inherit;
  background: linear-gradient(135deg, #6366f1, #ec4899, #f59e0b, #6366f1);
  background-size: 200% 200%;
  z-index: -1;
  animation: borderSpin 3s linear infinite;
}
@keyframes borderSpin { to { background-position: 200% 200%; } }
Visual FX
#41 CSS Only

Floating Label Input

Label floats above the input when focused or filled — Material-style.

Attribute
data-float-label
CSS
[data-float-label] { position: relative; }
[data-float-label] label {
  position: absolute;
  left: 12px; top: 50%;
  transform: translateY(-50%);
  font-size: 1rem;
  color: #888;
  pointer-events: none;
  transition: 0.2s ease;
}
[data-float-label] input:focus + label,
[data-float-label] input:not(:placeholder-shown) + label {
  top: 0; font-size: 0.72rem; color: #6366f1;
  background: white; padding: 0 4px;
}
Forms
#42 CSS + JS

Password Strength Meter

Shows a color-coded strength bar as the user types a password.

Attribute
data-strength-meter
CSS
[data-strength-bar] {
  height: 4px;
  border-radius: 2px;
  background: #e5e7eb;
  overflow: hidden;
}
[data-strength-bar]::after {
  content: '';
  display: block;
  height: 100%;
  width: var(--strength, 0%);
  background: var(--strength-color, #ef4444);
  transition: width 0.3s ease, background 0.3s ease;
  border-radius: 2px;
}
JS JavaScript
const meter = document.querySelector('[data-strength-meter]');
const bar = document.querySelector('[data-strength-bar]');
if (meter && bar) {
  meter.addEventListener('input', () => {
    const v = meter.value;
    let score = 0;
    if (v.length >= 8) score++;
    if (/[A-Z]/.test(v)) score++;
    if (/[0-9]/.test(v)) score++;
    if (/[^A-Za-z0-9]/.test(v)) score++;
    const colors = ['#ef4444','#f97316','#facc15','#22c55e'];
    bar.style.setProperty('--strength', (score * 25) + '%');
    bar.style.setProperty('--strength-color', colors[score - 1] || '#ef4444');
  });
}
Forms
#43 CSS + JS

Character Counter

Live character count below a textarea. Set max via data-max='280'.

Attribute
data-char-count data-max="280"
CSS
[data-char-display] { font-size: 0.75rem; color: #888; text-align: right; }
[data-char-display].over { color: #ef4444; }
JS JavaScript
document.querySelectorAll('[data-char-count]').forEach(el => {
  const max = parseInt(el.dataset.max, 10) || 0;
  const display = document.querySelector(`[data-char-display="${el.id}"]`);
  if (!display) return;
  el.addEventListener('input', () => {
    const len = el.value.length;
    display.textContent = max ? `${len} / ${max}` : len;
    display.classList.toggle('over', max > 0 && len > max);
  });
});
Forms
#44 CSS + JS

Shrink Navbar on Scroll

Navbar shrinks in padding and adds a background when the user scrolls past 80px.

Attribute
data-navbar="shrink"
CSS
[data-navbar="shrink"] {
  transition: padding 0.3s ease, background 0.3s ease, box-shadow 0.3s ease;
}
[data-navbar="shrink"].scrolled {
  padding-top: 0.5rem !important;
  padding-bottom: 0.5rem !important;
  background: rgba(255,255,255,0.95);
  backdrop-filter: blur(8px);
  box-shadow: 0 2px 20px rgba(0,0,0,0.06);
}
JS JavaScript
const nav = document.querySelector('[data-navbar="shrink"]');
if (nav) {
  window.addEventListener('scroll', () => {
    nav.classList.toggle('scrolled', window.scrollY > 80);
  }, { passive: true });
}
Navigation
#45 CSS + JS

Active Nav Link Highlighter

Automatically marks the nav link matching the current URL as active.

Attribute
data-nav-link
CSS
[data-nav-link].active {
  color: var(--accent, #6366f1);
  font-weight: 700;
}
JS JavaScript
document.querySelectorAll('[data-nav-link]').forEach(link => {
  if (link.href === window.location.href ||
      window.location.pathname.startsWith(new URL(link.href).pathname) && link.href !== '/') {
    link.classList.add('active');
  }
});
Navigation
#46 CSS + JS

Disable Right Click

Prevents right-click context menu on images or sections to protect content.

Attribute
data-no-rightclick
CSS
[data-no-rightclick] { user-select: none; -webkit-user-drag: none; }
JS JavaScript
document.querySelectorAll('[data-no-rightclick]').forEach(el => {
  el.addEventListener('contextmenu', e => e.preventDefault());
});
Utility
#47 CSS + JS

Auto External Link Icons

Appends an external arrow icon and target='_blank' to all links inside a container.

Attribute
data-external-links
CSS
[data-external-links] a[target="_blank"]::after {
  content: ' ↗';
  font-size: 0.8em;
  opacity: 0.5;
}
JS JavaScript
document.querySelectorAll('[data-external-links] a').forEach(link => {
  try {
    if (new URL(link.href).hostname !== window.location.hostname) {
      link.setAttribute('target', '_blank');
      link.setAttribute('rel', 'noopener noreferrer');
    }
  } catch(e) {}
});
Utility
#48 CSS Only

Print Friendly Section

Hides all other content and only prints the marked section.

Attribute
data-print-only
CSS
@media print {
  body > *:not([data-print-only]) { display: none !important; }
  [data-print-only] { display: block !important; }
}
Utility
#49 JS Only

Keyboard Shortcut Trigger

Triggers a click on the element when a keyboard key is pressed. Set key in attribute.

Attribute
data-hotkey="k"
JS JavaScript
document.querySelectorAll('[data-hotkey]').forEach(el => {
  document.addEventListener('keydown', e => {
    if ((e.ctrlKey || e.metaKey) && e.key === el.dataset.hotkey) {
      e.preventDefault();
      el.click();
    }
  });
});
Utility
#50 JS Only

Confetti Burst on Click

Fires a confetti animation burst from the clicked button position. Party time.

Attribute
data-confetti
CSS
@keyframes confettiFly {
  to { transform: translateY(-200px) rotate(720deg); opacity: 0; }
}
JS JavaScript
document.querySelectorAll('[data-confetti]').forEach(btn => {
  btn.addEventListener('click', e => {
    const colors = ['#6366f1','#ec4899','#f59e0b','#22c55e','#ef4444','#3b82f6'];
    for (let i = 0; i < 30; i++) {
      const dot = document.createElement('span');
      Object.assign(dot.style, {
        position: 'fixed',
        left: e.clientX + 'px', top: e.clientY + 'px',
        width: Math.random() * 8 + 4 + 'px',
        height: Math.random() * 8 + 4 + 'px',
        background: colors[Math.floor(Math.random() * colors.length)],
        borderRadius: Math.random() > 0.5 ? '50%' : '2px',
        pointerEvents: 'none', zIndex: 9999,
        transform: `translate(${(Math.random()-0.5)*80}px,0)`,
        animation: `confettiFly ${Math.random()*0.5+0.5}s ease-out forwards`,
      });
      document.body.appendChild(dot);
      dot.addEventListener('animationend', () => dot.remove());
    }
  });
});
Utility
#51 CSS Only

Blur In on Scroll

Elements sharpen into focus as they enter the viewport. Great for image reveals.

Attribute
data-animate="blur-in"
CSS
[data-animate="blur-in"] {
  opacity: 0;
  filter: blur(12px);
  animation: blurIn linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 30%;
}
@keyframes blurIn { to { opacity: 1; filter: blur(0); } }
Animations
#52 CSS Only

Scale Up on Scroll

Elements grow from 80% to full size as they enter view.

Attribute
data-animate="scale-up"
CSS
[data-animate="scale-up"] {
  opacity: 0;
  transform: scale(0.8);
  animation: scaleUp linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 30%;
}
@keyframes scaleUp { to { opacity: 1; transform: scale(1); } }
Animations
#53 CSS Only

Slide In from Left

Element sweeps in from the left side of the screen on scroll.

Attribute
data-animate="slide-left"
CSS
[data-animate="slide-left"] {
  opacity: 0;
  transform: translateX(-60px);
  animation: slideLeft linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 30%;
}
@keyframes slideLeft { to { opacity: 1; transform: none; } }
Animations
#54 CSS Only

Slide In from Right

Element sweeps in from the right side on scroll.

Attribute
data-animate="slide-right"
CSS
[data-animate="slide-right"] {
  opacity: 0;
  transform: translateX(60px);
  animation: slideRight linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 30%;
}
@keyframes slideRight { to { opacity: 1; transform: none; } }
Animations
#55 CSS Only

Flip In on Scroll

Element flips from flat (rotateX 90°) into full view as it enters the viewport.

Attribute
data-animate="flip-in"
CSS
[data-animate="flip-in"] {
  opacity: 0;
  transform: rotateX(90deg);
  transform-origin: top center;
  animation: flipIn linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 35%;
}
@keyframes flipIn { to { opacity: 1; transform: rotateX(0); } }
Animations
#56 CSS Only

Bounce In

Element bounces into view when it enters the viewport.

Attribute
data-animate="bounce-in"
CSS
[data-animate="bounce-in"] {
  opacity: 0;
  animation: bounceIn linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 20%;
}
@keyframes bounceIn {
  0%  { opacity: 0; transform: scale(0.3); }
  50% { opacity: 1; transform: scale(1.05); }
  70% { transform: scale(0.95); }
  100%{ transform: scale(1); }
}
Animations
#57 CSS Only

Infinite Float

Element bobs up and down continuously — good for hero images and icons.

Attribute
data-float
CSS
[data-float] {
  animation: floatLoop 3s ease-in-out infinite;
}
@keyframes floatLoop {
  0%, 100% { transform: translateY(0); }
  50%       { transform: translateY(-12px); }
}
Animations
#58 CSS Only

Spin Loader

Spins the element continuously. Use on icon spinners or loading states.

Attribute
data-spin
CSS
[data-spin] {
  animation: spinLoop 1s linear infinite;
}
@keyframes spinLoop { to { transform: rotate(360deg); } }
Animations
#59 CSS Only

Heartbeat Pulse

Element beats like a heart. Great for like/favourite icons.

Attribute
data-heartbeat
CSS
[data-heartbeat] {
  animation: heartbeat 1.2s ease-in-out infinite;
}
@keyframes heartbeat {
  0%, 100% { transform: scale(1); }
  14%       { transform: scale(1.3); }
  28%       { transform: scale(1); }
  42%       { transform: scale(1.2); }
  70%       { transform: scale(1); }
}
Animations
#60 CSS Only

Blink Attention

Blinks the element to draw attention — like a notification badge.

Attribute
data-blink
CSS
[data-blink] {
  animation: blinkAnim 1s step-end infinite;
}
@keyframes blinkAnim {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0; }
}
Animations
#61 CSS + JS

Reveal on Scroll (IntersectionObserver)

Classic JS-based scroll reveal using IntersectionObserver. Add to any element.

Attribute
data-reveal
CSS
[data-reveal] {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
[data-reveal].is-visible {
  opacity: 1;
  transform: none;
}
JS JavaScript
new IntersectionObserver((entries) => {
  entries.forEach(e => {
    if (e.isIntersecting) e.target.classList.add('is-visible');
  });
}, { threshold: 0.15 }).observe(...document.querySelectorAll('[data-reveal]'));
Scroll
#62 CSS + JS

Scroll-Linked Opacity

Element fades out as you scroll past it — like a hero vanishing into the page.

Attribute
data-scroll-fade
CSS
[data-scroll-fade] { transition: opacity 0.1s linear; }
JS JavaScript
document.querySelectorAll('[data-scroll-fade]').forEach(el => {
  window.addEventListener('scroll', () => {
    const r = el.getBoundingClientRect();
    const pct = Math.max(0, Math.min(1, 1 - r.bottom / window.innerHeight));
    el.style.opacity = 1 - pct;
  }, { passive: true });
});
Scroll
#63 CSS + JS

Section Active Class

Adds 'is-active' to nav links matching the section currently in view.

Attribute
data-section-id="about"
CSS
[data-nav-item].is-active {
  color: var(--accent, #6366f1);
  font-weight: 700;
}
JS JavaScript
const sections = document.querySelectorAll('[data-section-id]');
const navItems = document.querySelectorAll('[data-nav-item]');
new IntersectionObserver(entries => {
  entries.forEach(e => {
    if (e.isIntersecting) {
      navItems.forEach(n => n.classList.toggle('is-active', n.dataset.navItem === e.target.dataset.sectionId));
    }
  });
}, { threshold: 0.5 }).observe(...sections);
Scroll
#64 CSS Only

Infinite Marquee

Scrolls child elements in a continuous horizontal loop — no JS required.

Attribute
data-marquee
CSS
[data-marquee] {
  overflow: hidden;
  display: flex;
}
[data-marquee] > * {
  display: flex;
  flex-shrink: 0;
  animation: marquee 20s linear infinite;
}
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-100%); }
}
Scroll
#65 CSS + JS

Scroll Velocity Tilt

Element tilts based on scroll velocity — faster scroll = more tilt.

Attribute
data-velocity-tilt
CSS
[data-velocity-tilt] { transition: transform 0.3s ease; }
JS JavaScript
let lastY = 0;
document.querySelectorAll('[data-velocity-tilt]').forEach(el => {
  window.addEventListener('scroll', () => {
    const vel = window.scrollY - lastY;
    el.style.transform = `rotateX(${Math.max(-8, Math.min(8, -vel * 0.3))}deg)`;
    lastY = window.scrollY;
  }, { passive: true });
});
Scroll
#66 CSS + JS

Cursor Scale on Hover

Custom cursor grows when hovering over elements with data-cursor-grow.

Attribute
data-cursor-grow
CSS
[data-cursor="dot"] {
  position: fixed;
  width: 8px; height: 8px;
  background: currentColor;
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;
  transition: transform 0.2s ease, width 0.2s ease, height 0.2s ease;
}
body:has([data-cursor-grow]:hover) [data-cursor="dot"] {
  transform: scale(4);
}
JS JavaScript
const dot = document.querySelector('[data-cursor="dot"]');
if (dot) {
  document.addEventListener('mousemove', e => {
    dot.style.left = (e.clientX - 4) + 'px';
    dot.style.top  = (e.clientY - 4) + 'px';
  });
}
Cursor
#67 CSS + JS

Cursor Trail Particles

Leaves fading dot particles as the cursor moves across the page.

Attribute
data-cursor-trail
CSS
@keyframes trailFade { to { opacity: 0; transform: scale(0); } }
JS JavaScript
document.addEventListener('mousemove', e => {
  const dot = document.createElement('span');
  Object.assign(dot.style, {
    position: 'fixed', left: e.clientX + 'px', top: e.clientY + 'px',
    width: '6px', height: '6px', borderRadius: '50%',
    background: `hsl(${Math.random()*360},80%,60%)`,
    pointerEvents: 'none', zIndex: 9999,
    animation: 'trailFade 0.6s ease forwards',
  });
  document.body.appendChild(dot);
  dot.addEventListener('animationend', () => dot.remove());
});
Cursor
#68 JS Only

Text Cursor Override

Changes the cursor to a custom SVG icon on hover.

Attribute
data-cursor-icon="🎯"
JS JavaScript
document.querySelectorAll('[data-cursor-icon]').forEach(el => {
  const icon = el.dataset.cursorIcon;
  const svg = `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32'><text y='26' font-size='26'>${icon}</text></svg>`;
  el.style.cursor = `url('${svg}') 16 16, auto`;
});
Cursor
#69 CSS + JS

Glitch Effect

Text glitches with displaced colour channels on hover. Perfect for dark/cyber themes.

Attribute
data-glitch
CSS
[data-glitch] {
  position: relative;
}
[data-glitch]:hover::before,
[data-glitch]:hover::after {
  content: attr(data-glitch);
  position: absolute;
  left: 0; top: 0;
  animation: glitch 0.3s infinite;
}
[data-glitch]:hover::before { color: #f0f; clip-path: polygon(0 30%,100% 30%,100% 50%,0 50%); }
[data-glitch]:hover::after  { color: #0ff; clip-path: polygon(0 55%,100% 55%,100% 75%,0 75%); left: 2px; }
@keyframes glitch {
  0%  { transform: translateX(0); }
  20% { transform: translateX(-3px); }
  40% { transform: translateX(3px); }
  60% { transform: translateX(-2px); }
  80% { transform: translateX(2px); }
  100%{ transform: translateX(0); }
}
JS JavaScript
document.querySelectorAll('[data-glitch]').forEach(el => {
  el.setAttribute('data-glitch', el.textContent ?? '');
});
Text
#70 CSS Only

Letter Spacing Hover

Letters expand on hover for a cinematic title effect.

Attribute
data-text="expand"
CSS
[data-text="expand"] {
  letter-spacing: 0em;
  transition: letter-spacing 0.4s ease;
}
[data-text="expand"]:hover { letter-spacing: 0.3em; }
Text
#71 CSS + JS

Rotating Text Cycle

Cycles through a list of words with a fade-swap animation.

Attribute
data-rotate-text="Design,Build,Ship"
CSS
[data-rotate-text] .rt-word {
  display: inline-block;
  animation: rtFade 0.4s ease;
}
@keyframes rtFade {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
JS JavaScript
document.querySelectorAll('[data-rotate-text]').forEach(el => {
  const words = el.dataset.rotateText.split(',');
  let i = 0;
  el.innerHTML = `<span class="rt-word">${words[0]}</span>`;
  setInterval(() => {
    i = (i + 1) % words.length;
    el.innerHTML = `<span class="rt-word">${words[i]}</span>`;
  }, 2000);
});
Text
#72 CSS + JS

Character Split Hover

Each character flies apart on hover then comes back together.

Attribute
data-char-split
CSS
[data-char-split] span {
  display: inline-block;
  transition: transform 0.3s ease;
}
[data-char-split]:hover span { transform: translateY(-6px) rotate(var(--r, 0deg)); }
JS JavaScript
document.querySelectorAll('[data-char-split]').forEach(el => {
  el.innerHTML = [...el.textContent].map((c, i) =>
    `<span style="--r:${(i%2===0?1:-1)*Math.random()*15}deg">${c === ' ' ? '&nbsp;' : c}</span>`
  ).join('');
});
Text
#73 CSS Only

Balance Headings

Uses CSS text-wrap: balance for perfectly even multi-line headings.

Attribute
data-text="balance"
CSS
[data-text="balance"] {
  text-wrap: balance;
}
Text
#74 CSS Only

Masked Reveal Wipe

Text reveals with a left-to-right clip-path wipe on scroll entry.

Attribute
data-wipe
CSS
[data-wipe] {
  clip-path: inset(0 100% 0 0);
  animation: wipe linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 40%;
}
@keyframes wipe { to { clip-path: inset(0 0% 0 0); } }
Text
#75 CSS + JS

Toast Notification

Shows a temporary toast message. Set message in attribute.

Attribute
data-toast="Saved successfully!"
CSS
[data-toast-msg] {
  position: fixed;
  bottom: 2rem; left: 50%;
  transform: translateX(-50%) translateY(60px);
  background: #111;
  color: #fff;
  padding: 0.65rem 1.25rem;
  border-radius: 8px;
  font-size: 0.85rem;
  z-index: 9999;
  opacity: 0;
  transition: transform 0.3s ease, opacity 0.3s ease;
  pointer-events: none;
}
[data-toast-msg].show {
  transform: translateX(-50%) translateY(0);
  opacity: 1;
}
JS JavaScript
function showToast(msg) {
  let el = document.querySelector('[data-toast-msg]');
  if (!el) {
    el = document.createElement('div');
    el.setAttribute('data-toast-msg', '');
    document.body.appendChild(el);
  }
  el.textContent = msg;
  el.classList.add('show');
  setTimeout(() => el.classList.remove('show'), 2500);
}
document.querySelectorAll('[data-toast]').forEach(btn => {
  btn.addEventListener('click', () => showToast(btn.dataset.toast));
});
UI Patterns
#76 CSS Only

Skeleton Loader

Animated shimmer placeholder shown while content loads.

Attribute
data-skeleton
CSS
[data-skeleton] {
  background: linear-gradient(90deg, var(--border) 25%, var(--border-med) 50%, var(--border) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.4s infinite;
  border-radius: 6px;
  color: transparent !important;
  pointer-events: none;
}
[data-skeleton] * { visibility: hidden; }
@keyframes shimmer { to { background-position: -200% 0; } }
UI Patterns
#77 CSS + JS

Draggable Element

Makes any element freely draggable with the mouse.

Attribute
data-draggable
CSS
[data-draggable] { cursor: grab; user-select: none; }
[data-draggable].dragging { cursor: grabbing; }
JS JavaScript
document.querySelectorAll('[data-draggable]').forEach(el => {
  let ox = 0, oy = 0, mx = 0, my = 0;
  el.addEventListener('mousedown', e => {
    e.preventDefault();
    ox = e.clientX - el.offsetLeft;
    oy = e.clientY - el.offsetTop;
    el.classList.add('dragging');
    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up, { once: true });
  });
  function move(e) {
    el.style.position = 'fixed';
    el.style.left = (e.clientX - ox) + 'px';
    el.style.top  = (e.clientY - oy) + 'px';
  }
  function up() {
    el.classList.remove('dragging');
    document.removeEventListener('mousemove', move);
  }
});
UI Patterns
#78 CSS + JS

Popover on Click

Shows a small popover box anchored to the clicked element.

Attribute
data-popover="Content goes here"
CSS
[data-popover-box] {
  position: fixed;
  background: #111;
  color: #fff;
  padding: 0.5rem 0.9rem;
  border-radius: 8px;
  font-size: 0.8rem;
  z-index: 9999;
  pointer-events: none;
  opacity: 0;
  transform: scale(0.9);
  transition: opacity 0.15s, transform 0.15s;
}
[data-popover-box].show { opacity: 1; transform: scale(1); }
JS JavaScript
let box = document.querySelector('[data-popover-box]');
if (!box) {
  box = document.createElement('div');
  box.setAttribute('data-popover-box', '');
  document.body.appendChild(box);
}
document.querySelectorAll('[data-popover]').forEach(el => {
  el.addEventListener('click', e => {
    const r = el.getBoundingClientRect();
    box.textContent = el.dataset.popover;
    box.style.left = r.left + 'px';
    box.style.top  = (r.bottom + 8) + 'px';
    box.classList.toggle('show');
  });
});
document.addEventListener('click', e => {
  if (!e.target.closest('[data-popover]')) box.classList.remove('show');
});
UI Patterns
#79 CSS + JS

Star Rating

Interactive 1–5 star rating. Stores value in data-rating on the wrapper.

Attribute
data-star-rating
CSS
[data-star-rating] { display: flex; gap: 4px; cursor: pointer; font-size: 1.5rem; }
[data-star-rating] span { color: #d1d5db; transition: color 0.15s; }
[data-star-rating] span.active { color: #facc15; }
JS JavaScript
document.querySelectorAll('[data-star-rating]').forEach(el => {
  const stars = Array.from({ length: 5 }, (_, i) => {
    const s = document.createElement('span');
    s.textContent = '★';
    s.dataset.val = String(i + 1);
    el.appendChild(s);
    return s;
  });
  stars.forEach(s => {
    s.addEventListener('click', () => {
      const v = Number(s.dataset.val);
      el.dataset.rating = String(v);
      stars.forEach((x, i) => x.classList.toggle('active', i < v));
    });
    s.addEventListener('mouseenter', () => {
      stars.forEach((x, i) => x.classList.toggle('active', i <= stars.indexOf(s)));
    });
  });
  el.addEventListener('mouseleave', () => {
    const v = Number(el.dataset.rating) || 0;
    stars.forEach((x, i) => x.classList.toggle('active', i < v));
  });
});
UI Patterns
#80 CSS + JS

Read More Toggle

Truncates text and expands on click. Set max lines with data-lines='3'.

Attribute
data-read-more data-lines="3"
CSS
[data-read-more] {
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--lines, 3);
  transition: -webkit-line-clamp 0s;
}
[data-read-more].expanded { -webkit-line-clamp: unset; }
JS JavaScript
document.querySelectorAll('[data-read-more]').forEach(el => {
  el.style.setProperty('--lines', el.dataset.lines || '3');
  const btn = document.createElement('button');
  btn.textContent = 'Read more';
  btn.style.cssText = 'display:block;margin-top:0.5rem;font-size:0.85rem;color:var(--accent,#6366f1);background:none;border:none;cursor:pointer;padding:0';
  el.after(btn);
  btn.addEventListener('click', () => {
    const open = el.classList.toggle('expanded');
    btn.textContent = open ? 'Read less' : 'Read more';
  });
});
UI Patterns
#81 CSS Only

Image Zoom on Hover

Image zooms smoothly inside its container on hover without changing layout.

Attribute
data-zoom
CSS
[data-zoom] { overflow: hidden; }
[data-zoom] img {
  transition: transform 0.4s ease;
  display: block;
  width: 100%;
}
[data-zoom]:hover img { transform: scale(1.08); }
UI Patterns
#82 CSS + JS

Before / After Slider

Drag to compare two images side by side with a resizable divider.

Attribute
data-before-after
CSS
[data-before-after] {
  position: relative;
  overflow: hidden;
  cursor: col-resize;
  user-select: none;
}
[data-before-after] .ba-after {
  position: absolute; inset: 0;
  clip-path: inset(0 0 0 50%);
  transition: clip-path 0s;
}
[data-before-after] .ba-handle {
  position: absolute; top: 0; bottom: 0;
  width: 3px; background: #fff;
  left: 50%; transform: translateX(-50%);
  cursor: col-resize;
}
JS JavaScript
document.querySelectorAll('[data-before-after]').forEach(el => {
  const after = el.querySelector('.ba-after');
  const handle = el.querySelector('.ba-handle');
  let dragging = false;
  el.addEventListener('mousedown', () => dragging = true);
  document.addEventListener('mouseup', () => dragging = false);
  document.addEventListener('mousemove', e => {
    if (!dragging) return;
    const r = el.getBoundingClientRect();
    const pct = Math.max(0, Math.min(100, ((e.clientX - r.left) / r.width) * 100));
    after.style.clipPath = `inset(0 0 0 ${pct}%)`;
    handle.style.left = pct + '%';
  });
});
UI Patterns
#83 JS Only

Infinite Scroll Trigger

Fires a custom event when a sentinel element enters view — wire to your load logic.

Attribute
data-infinite-trigger
JS JavaScript
const trigger = document.querySelector('[data-infinite-trigger]');
if (trigger) {
  new IntersectionObserver(([e]) => {
    if (e.isIntersecting) trigger.dispatchEvent(new CustomEvent('load-more', { bubbles: true }));
  }, { rootMargin: '200px' }).observe(trigger);
  trigger.addEventListener('load-more', () => {
    console.log('Load next page of content');
  });
}
UI Patterns
#84 CSS + JS

Scroll Lock Body

Prevents body scroll when this element is open — for modals and drawers.

Attribute
data-scroll-lock
CSS
body.scroll-locked { overflow: hidden; }
JS JavaScript
document.querySelectorAll('[data-scroll-lock]').forEach(el => {
  const observer = new MutationObserver(() => {
    document.body.classList.toggle('scroll-locked', el.classList.contains('is-open'));
  });
  observer.observe(el, { attributes: true, attributeFilter: ['class'] });
});
UI Patterns
#85 JS Only

Keyboard Dismiss (Escape)

Closes modals / drawers when the Escape key is pressed.

Attribute
data-escape-close
JS JavaScript
document.addEventListener('keydown', e => {
  if (e.key !== 'Escape') return;
  document.querySelectorAll('[data-escape-close].is-open').forEach(el => {
    el.classList.remove('is-open');
  });
});
UI Patterns
#86 CSS Only

Frosted Card

Blurred glass card with a subtle inner border — clean on gradient backgrounds.

Attribute
data-frosted
CSS
[data-frosted] {
  background: rgba(255,255,255,0.06);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.18);
}
Visual FX
#87 CSS Only

Aurora Background

Animated soft colour blobs in the background — like the aurora borealis.

Attribute
data-aurora
CSS
[data-aurora] {
  position: relative;
  overflow: hidden;
}
[data-aurora]::before,
[data-aurora]::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  filter: blur(80px);
  opacity: 0.35;
  animation: auroraMove 8s ease-in-out infinite alternate;
}
[data-aurora]::before { width: 60%; padding-bottom: 60%; background: #6366f1; top: -20%; left: -10%; }
[data-aurora]::after  { width: 50%; padding-bottom: 50%; background: #ec4899; bottom: -20%; right: -10%; animation-delay: -4s; }
@keyframes auroraMove {
  from { transform: translate(0,0) scale(1); }
  to   { transform: translate(5%,10%) scale(1.1); }
}
Visual FX
#88 CSS Only

Mesh Gradient Background

Generates a colourful mesh gradient from a few colour stops as a background.

Attribute
data-mesh
CSS
[data-mesh] {
  background:
    radial-gradient(at 0% 0%,   #6366f1 0px, transparent 50%),
    radial-gradient(at 100% 0%,  #ec4899 0px, transparent 50%),
    radial-gradient(at 100% 100%,#f59e0b 0px, transparent 50%),
    radial-gradient(at 0% 100%,  #22c55e 0px, transparent 50%);
}
Visual FX
#89 CSS Only

Neumorphism Card

Soft neumorphic card style with inset/raised shadow on light backgrounds.

Attribute
data-neumorphic
CSS
[data-neumorphic] {
  background: #e0e5ec;
  border-radius: 16px;
  box-shadow:
    6px 6px 12px #b8bec7,
    -6px -6px 12px #ffffff;
}
Visual FX
#90 CSS + JS

SVG Filter Duotone

Applies a two-colour filter to images using an SVG feColorMatrix trick.

Attribute
data-duotone
CSS
[data-duotone] {
  filter: url(#duotone);
}
JS JavaScript
if (!document.getElementById('duotone-filter')) {
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  svg.setAttribute('id', 'duotone-filter');
  svg.style.cssText = 'position:absolute;width:0;height:0';
  svg.innerHTML = `<defs><filter id="duotone">
    <feColorMatrix type="saturate" values="0"/>
    <feComponentTransfer>
      <feFuncR type="table" tableValues="0.1 0.9"/>
      <feFuncG type="table" tableValues="0.05 0.3"/>
      <feFuncB type="table" tableValues="0.5 0.1"/>
    </feComponentTransfer>
  </filter></defs>`;
  document.body.prepend(svg);
}
Visual FX
#91 CSS + JS

Ripple Effect on Click

Material-style ripple that expands from the click point on any element.

Attribute
data-ripple
CSS
[data-ripple] { position: relative; overflow: hidden; }
.ripple-wave {
  position: absolute;
  border-radius: 50%;
  background: rgba(255,255,255,0.3);
  transform: scale(0);
  animation: rippleExpand 0.6s linear;
  pointer-events: none;
}
@keyframes rippleExpand {
  to { transform: scale(4); opacity: 0; }
}
JS JavaScript
document.querySelectorAll('[data-ripple]').forEach(el => {
  el.addEventListener('click', e => {
    const r = el.getBoundingClientRect();
    const size = Math.max(r.width, r.height);
    const wave = document.createElement('span');
    wave.className = 'ripple-wave';
    Object.assign(wave.style, {
      width: size + 'px', height: size + 'px',
      left: (e.clientX - r.left - size / 2) + 'px',
      top:  (e.clientY - r.top  - size / 2) + 'px',
    });
    el.appendChild(wave);
    wave.addEventListener('animationend', () => wave.remove());
  });
});
Visual FX
#92 CSS Only

Gradient Glow Shadow

Creates a glowing drop shadow that matches the element's background colour.

Attribute
data-glow-shadow
CSS
[data-glow-shadow] {
  box-shadow:
    0 0 20px -5px var(--glow-color, #6366f1),
    0 0 60px -10px var(--glow-color, #6366f1);
}
Visual FX
#93 CSS Only

CSS Neon Text

Glowing neon text using layered text-shadow.

Attribute
data-neon
CSS
[data-neon] {
  color: #fff;
  text-shadow:
    0 0 5px  #fff,
    0 0 10px #fff,
    0 0 20px var(--neon-color, #0ff),
    0 0 40px var(--neon-color, #0ff),
    0 0 80px var(--neon-color, #0ff);
}
Visual FX
#94 CSS Only

CSS Dot Grid Background

Adds a subtle dot grid pattern as a background on any section.

Attribute
data-bg="dots"
CSS
[data-bg="dots"] {
  background-image: radial-gradient(circle, rgba(0,0,0,0.12) 1px, transparent 1px);
  background-size: 24px 24px;
}
Visual FX
#95 CSS Only

CSS Line Grid Background

Adds a subtle line grid (graph paper) pattern as a background.

Attribute
data-bg="grid"
CSS
[data-bg="grid"] {
  background-image:
    linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(0,0,0,0.05) 1px, transparent 1px);
  background-size: 32px 32px;
}
Visual FX
#96 CSS Only

Auto Fill Grid

Responsive grid that auto-fills columns at a minimum width — no media queries.

Attribute
data-grid="auto"
CSS
[data-grid="auto"] {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--min, 240px), 1fr));
  gap: var(--gap, 1.5rem);
}
Layout
#97 CSS Only

Centre Everything

Absolutely centres child content both horizontally and vertically.

Attribute
data-center
CSS
[data-center] {
  display: grid;
  place-items: center;
}
Layout
#98 CSS Only

Fluid Typography Scale

Font size scales fluidly between two viewport sizes with no breakpoints.

Attribute
data-fluid-type
CSS
[data-fluid-type] {
  font-size: clamp(1rem, 2.5vw + 0.5rem, 2.5rem);
  line-height: 1.2;
}
Layout
#99 CSS Only

Container Query Card

Card that changes layout based on its own width, not the viewport.

Attribute
data-cq
CSS
[data-cq] {
  container-type: inline-size;
}
@container (min-width: 400px) {
  [data-cq] .cq-inner {
    display: flex;
    gap: 1rem;
  }
}
Layout
#100 CSS Only

Sidebar + Main Layout

Classic holy grail layout: sidebar fixed-width, main fills remaining space.

Attribute
data-layout="sidebar"
CSS
[data-layout="sidebar"] {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: 2rem;
  align-items: start;
}
@media (max-width: 768px) {
  [data-layout="sidebar"] { grid-template-columns: 1fr; }
}
Layout
#101 CSS Only

Breadcrumb Auto-Separator

Auto-inserts › separators between breadcrumb items using CSS.

Attribute
data-breadcrumb
CSS
[data-breadcrumb] {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.25rem;
  font-size: 0.85rem;
}
[data-breadcrumb] a + a::before {
  content: '›';
  margin-right: 0.25rem;
  color: var(--text-faint);
}
Navigation
#102 JS Only

Mobile Swipe Close Drawer

Detects a left swipe on a drawer element and closes it.

Attribute
data-swipe-close
JS JavaScript
document.querySelectorAll('[data-swipe-close]').forEach(el => {
  let startX = 0;
  el.addEventListener('touchstart', e => { startX = e.touches[0].clientX; }, { passive: true });
  el.addEventListener('touchend', e => {
    if (startX - e.changedTouches[0].clientX > 60) el.classList.remove('is-open');
  });
});
Navigation
#103 CSS + JS

Dot Pagination Indicator

Updates active dot in a pagination strip based on the current slide index.

Attribute
data-dot-nav
CSS
[data-dot-nav] { display: flex; gap: 0.5rem; }
[data-dot-nav] button {
  width: 8px; height: 8px;
  border-radius: 50%; border: none;
  background: var(--border-med);
  transition: background 0.2s, transform 0.2s;
  cursor: pointer; padding: 0;
}
[data-dot-nav] button.active {
  background: var(--text);
  transform: scale(1.3);
}
JS JavaScript
document.querySelectorAll('[data-dot-nav]').forEach(nav => {
  const dots = nav.querySelectorAll('button');
  dots.forEach((dot, i) => {
    dot.addEventListener('click', () => {
      dots.forEach(d => d.classList.remove('active'));
      dot.classList.add('active');
      nav.dispatchEvent(new CustomEvent('dot-change', { detail: i, bubbles: true }));
    });
  });
  dots[0]?.classList.add('active');
});
Navigation
#104 CSS + JS

Input Auto-Resize Textarea

Textarea grows in height as the user types — no scrollbars.

Attribute
data-autoresize
CSS
[data-autoresize] {
  resize: none;
  overflow: hidden;
  min-height: 48px;
  transition: height 0.1s ease;
}
JS JavaScript
document.querySelectorAll('[data-autoresize]').forEach(el => {
  const resize = () => {
    el.style.height = 'auto';
    el.style.height = el.scrollHeight + 'px';
  };
  el.addEventListener('input', resize);
  resize();
});
Forms
#105 CSS Only

Input Validation State

Adds green/red border and icon based on HTML5 validity state.

Attribute
data-validate
CSS
[data-validate]:valid   { border-color: #22c55e; box-shadow: 0 0 0 2px rgba(34,197,94,0.15); }
[data-validate]:invalid { border-color: #ef4444; box-shadow: 0 0 0 2px rgba(239,68,68,0.15); }
[data-validate]:placeholder-shown { border-color: var(--border-med); box-shadow: none; }
Forms
#106 CSS + JS

Toggle Password Visibility

Eye button that toggles password input between text and password type.

Attribute
data-pwd-toggle
CSS
[data-pwd-wrap] { position: relative; }
[data-pwd-toggle] {
  position: absolute; right: 0.75rem; top: 50%;
  transform: translateY(-50%);
  background: none; border: none;
  cursor: pointer; color: var(--text-faint);
  font-size: 0.8rem;
}
JS JavaScript
document.querySelectorAll('[data-pwd-toggle]').forEach(btn => {
  const input = btn.closest('[data-pwd-wrap]')?.querySelector('input');
  if (!input) return;
  btn.addEventListener('click', () => {
    const isText = input.type === 'text';
    input.type = isText ? 'password' : 'text';
    btn.textContent = isText ? '👁' : '🙈';
  });
});
Forms
#107 CSS + JS

Numeric Stepper

Plus/minus buttons that increment or decrement a number input.

Attribute
data-stepper
CSS
[data-stepper] { display: inline-flex; align-items: center; gap: 0; border: 1px solid var(--border-med); border-radius: 8px; overflow: hidden; }
[data-stepper] button { background: var(--border); border: none; padding: 0.4rem 0.7rem; cursor: pointer; font-size: 1rem; color: var(--text); }
[data-stepper] input  { width: 3rem; text-align: center; border: none; background: none; font-size: 0.9rem; color: var(--text); }
JS JavaScript
document.querySelectorAll('[data-stepper]').forEach(el => {
  const input = el.querySelector('input');
  el.querySelectorAll('button').forEach(btn => {
    btn.addEventListener('click', () => {
      const step = Number(input.step) || 1;
      const val  = Number(input.value) || 0;
      input.value = String(btn.dataset.dir === '+' ? val + step : val - step);
    });
  });
});
Forms
#108 CSS + JS

Range Slider Live Value

Shows the current range input value in a live tooltip above the thumb.

Attribute
data-range-live
CSS
[data-range-wrap] { position: relative; }
[data-range-output] {
  position: absolute;
  bottom: calc(100% + 4px);
  left: var(--pct, 50%);
  transform: translateX(-50%);
  background: #111;
  color: #fff;
  font-size: 0.7rem;
  padding: 0.15rem 0.4rem;
  border-radius: 4px;
  pointer-events: none;
}
JS JavaScript
document.querySelectorAll('[data-range-live]').forEach(input => {
  const wrap = input.closest('[data-range-wrap]') || input.parentElement;
  const out = wrap?.querySelector('[data-range-output]');
  const update = () => {
    const pct = ((Number(input.value) - Number(input.min)) / (Number(input.max) - Number(input.min))) * 100;
    if (out) { out.textContent = input.value; out.style.setProperty('--pct', pct + '%'); }
  };
  input.addEventListener('input', update);
  update();
});
Forms
#109 CSS + JS

Debounced Search Filter

Filters a list of items in real time as the user types in a search box.

Attribute
data-search-filter="#list-id"
CSS
[data-filter-item].hidden { display: none; }
JS JavaScript
document.querySelectorAll('[data-search-filter]').forEach(input => {
  const list = document.querySelector(input.dataset.searchFilter);
  if (!list) return;
  let timer;
  input.addEventListener('input', () => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      const q = input.value.toLowerCase();
      list.querySelectorAll('[data-filter-item]').forEach(item => {
        item.classList.toggle('hidden', !item.textContent.toLowerCase().includes(q));
      });
    }, 200);
  });
});
Utility
#110 JS Only

Share via Web Share API

Uses the native share sheet on mobile to share the page URL.

Attribute
data-share
JS JavaScript
document.querySelectorAll('[data-share]').forEach(btn => {
  if (!navigator.share) { btn.style.display = 'none'; return; }
  btn.addEventListener('click', () => {
    navigator.share({
      title: document.title,
      url: location.href,
    }).catch(() => {});
  });
});
Utility
#111 CSS + JS

Auto Anchor Headings

Adds a clickable # link to every heading inside the marked container.

Attribute
data-anchor-headings
CSS
[data-anchor-headings] :is(h1,h2,h3,h4) .anchor {
  opacity: 0;
  margin-left: 0.5rem;
  text-decoration: none;
  color: var(--text-faint);
  font-size: 0.85em;
  transition: opacity 0.2s;
}
[data-anchor-headings] :is(h1,h2,h3,h4):hover .anchor { opacity: 1; }
JS JavaScript
document.querySelectorAll('[data-anchor-headings]').forEach(el => {
  el.querySelectorAll('h1,h2,h3,h4').forEach(h => {
    if (!h.id) h.id = h.textContent.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
    const a = document.createElement('a');
    a.className = 'anchor';
    a.href = '#' + h.id;
    a.textContent = '#';
    h.appendChild(a);
  });
});
Utility
#112 JS Only

Timestamp to Relative Time

Converts ISO date strings to relative text like '3 hours ago'.

Attribute
data-relative-time
JS JavaScript
function relTime(date) {
  const diff = (Date.now() - new Date(date).getTime()) / 1000;
  const units = [['year',31536000],['month',2592000],['day',86400],['hour',3600],['minute',60],['second',1]];
  for (const [name, sec] of units) {
    const n = Math.floor(diff / sec);
    if (n >= 1) return `${n} ${name}${n > 1 ? 's' : ''} ago`;
  }
  return 'just now';
}
document.querySelectorAll('[data-relative-time]').forEach(el => {
  el.textContent = relTime(el.getAttribute('datetime') || el.textContent);
});
Utility
#113 CSS + JS

Prevent Double Submit

Disables a form's submit button after the first click to prevent duplicate submissions.

Attribute
data-once
CSS
[data-once][disabled] { opacity: 0.5; cursor: not-allowed; }
JS JavaScript
document.querySelectorAll('form:has([data-once])').forEach(form => {
  form.addEventListener('submit', () => {
    form.querySelectorAll('[data-once]').forEach(btn => {
      btn.disabled = true;
      btn.textContent = btn.dataset.loadingText || 'Submitting…';
    });
  });
});
Utility
#114 JS Only

Element Resize Observer

Fires a custom event when an element's size changes — useful for responsive logic.

Attribute
data-resize-watch
JS JavaScript
document.querySelectorAll('[data-resize-watch]').forEach(el => {
  new ResizeObserver(([entry]) => {
    el.dispatchEvent(new CustomEvent('resized', {
      detail: { width: entry.contentRect.width, height: entry.contentRect.height },
      bubbles: true,
    }));
  }).observe(el);
  el.addEventListener('resized', e => console.log('Resized:', e.detail));
});
Utility
#115 JS Only

Print Button

Triggers the browser print dialog when clicked.

Attribute
data-print
JS JavaScript
document.querySelectorAll('[data-print]').forEach(btn => {
  btn.addEventListener('click', () => window.print());
});
Utility
#116 JS Only

Idle Timeout Warning

Shows a warning after N seconds of inactivity. Set seconds in attribute.

Attribute
data-idle="30"
JS JavaScript
document.querySelectorAll('[data-idle]').forEach(el => {
  const secs = parseInt(el.dataset.idle, 10) * 1000;
  let timer;
  const reset = () => {
    clearTimeout(timer);
    timer = setTimeout(() => el.dispatchEvent(new CustomEvent('idle', { bubbles: true })), secs);
  };
  ['mousemove','keydown','scroll','touchstart'].forEach(ev => document.addEventListener(ev, reset, { passive: true }));
  el.addEventListener('idle', () => alert('Are you still there?'));
  reset();
});
Utility
#117 JS Only

Redirect After Delay

Redirects to a URL after N seconds. Good for thank-you pages.

Attribute
data-redirect="/home" data-delay="3"
JS JavaScript
document.querySelectorAll('[data-redirect]').forEach(el => {
  const delay = (parseFloat(el.dataset.delay) || 3) * 1000;
  setTimeout(() => location.href = el.dataset.redirect, delay);
});
Utility
#118 CSS + JS

Detect Online / Offline

Adds/removes an 'is-offline' class on the body when connectivity changes.

Attribute
data-connectivity
CSS
body.is-offline [data-connectivity]::after {
  content: 'You are offline';
  display: block;
  background: #ef4444;
  color: #fff;
  text-align: center;
  padding: 0.5rem;
  font-size: 0.85rem;
}
JS JavaScript
window.addEventListener('offline', () => document.body.classList.add('is-offline'));
window.addEventListener('online',  () => document.body.classList.remove('is-offline'));
Utility
#119 JS Only

Locale Number Format

Formats numbers inside elements with locale-aware separators (1,000,000).

Attribute
data-format-number
JS JavaScript
document.querySelectorAll('[data-format-number]').forEach(el => {
  const n = parseFloat(el.textContent.replace(/[^0-9.-]/g, ''));
  if (!isNaN(n)) el.textContent = n.toLocaleString();
});
Utility
#120 JS Only

Focus Trap

Traps keyboard focus inside a modal or drawer when it's open.

Attribute
data-focus-trap
JS JavaScript
document.querySelectorAll('[data-focus-trap]').forEach(el => {
  el.addEventListener('keydown', e => {
    if (e.key !== 'Tab') return;
    const focusable = [...el.querySelectorAll('a,button,input,select,textarea,[tabindex]:not([tabindex="-1"])')].filter(f => !f.disabled);
    const first = focusable[0], last = focusable[focusable.length - 1];
    if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
    else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
  });
});
Utility
#121 JS Only

Preload on Hover

Prefetches a linked page when the user hovers over a link, reducing navigation lag.

Attribute
data-prefetch
JS JavaScript
document.querySelectorAll('a[data-prefetch]').forEach(link => {
  link.addEventListener('mouseenter', () => {
    const rel = document.createElement('link');
    rel.rel = 'prefetch';
    rel.href = link.href;
    document.head.appendChild(rel);
  }, { once: true });
});
Performance
#122 JS Only

Defer Non-Critical Script

Loads a third-party script tag only after the page is idle using requestIdleCallback.

Attribute
data-defer-src="https://example.com/script.js"
JS JavaScript
document.querySelectorAll('[data-defer-src]').forEach(el => {
  const load = () => {
    const s = document.createElement('script');
    s.src = el.dataset.deferSrc;
    document.body.appendChild(s);
  };
  'requestIdleCallback' in window ? requestIdleCallback(load) : setTimeout(load, 2000);
});
Performance
#123 CSS Only

Will-Change Hint

Tells the browser to promote an element to its own layer before animating.

Attribute
data-will-change="transform"
CSS
[data-will-change] { will-change: var(--wc, transform); }
[data-will-change="opacity"]   { --wc: opacity; }
[data-will-change="transform"] { --wc: transform; }
[data-will-change="scroll"]    { --wc: scroll-position; }
Performance
#124 CSS Only

Content Visibility Auto

Skips rendering off-screen sections until they're needed — big perf win for long pages.

Attribute
data-cv-auto
CSS
[data-cv-auto] {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px;
}
Performance
#125 JS Only

Resource Hint DNS Prefetch

Injects a DNS-prefetch hint for a third-party domain to speed up connections.

Attribute
data-dns-prefetch="fonts.googleapis.com"
JS JavaScript
document.querySelectorAll('[data-dns-prefetch]').forEach(el => {
  const link = document.createElement('link');
  link.rel = 'dns-prefetch';
  link.href = '//' + el.dataset.dnsPrefetch;
  document.head.appendChild(link);
});
Performance
#126 CSS Only

Skip to Content Link

Hidden link that appears on Tab press, allowing keyboard users to skip navigation.

Attribute
data-skip-link
CSS
[data-skip-link] {
  position: fixed;
  top: -100%;
  left: 1rem;
  background: var(--text);
  color: var(--bg);
  padding: 0.5rem 1rem;
  border-radius: 0 0 6px 6px;
  font-size: 0.85rem;
  z-index: 9999;
  transition: top 0.2s ease;
  text-decoration: none;
}
[data-skip-link]:focus { top: 0; }
Accessibility
#127 CSS + JS

Announce Live Region

Pushes a message to screen readers via an ARIA live region.

Attribute
data-announce
CSS
[data-live-region] {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
}
JS JavaScript
let liveEl = document.querySelector('[data-live-region]');
if (!liveEl) {
  liveEl = document.createElement('div');
  liveEl.setAttribute('data-live-region', '');
  liveEl.setAttribute('aria-live', 'polite');
  liveEl.setAttribute('aria-atomic', 'true');
  document.body.appendChild(liveEl);
}
document.querySelectorAll('[data-announce]').forEach(el => {
  el.addEventListener('click', () => {
    liveEl.textContent = '';
    requestAnimationFrame(() => { liveEl.textContent = el.dataset.announce; });
  });
});
Accessibility
#128 CSS Only

Reduced Motion Aware

Disables all animations for users who prefer reduced motion at the system level.

Attribute
data-motion-safe
CSS
@media (prefers-reduced-motion: reduce) {
  [data-motion-safe] *,
  [data-motion-safe] *::before,
  [data-motion-safe] *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
Accessibility
#129 CSS Only

High Contrast Mode

Switches a section to high contrast colours for better readability.

Attribute
data-high-contrast
CSS
[data-high-contrast] {
  --text: #000;
  --bg: #fff;
  --text-muted: #000;
  --border: #000;
  background: #fff;
  color: #000;
}
html[data-theme="dark"] [data-high-contrast] {
  --text: #fff;
  --bg: #000;
  background: #000;
  color: #fff;
}
Accessibility
#130 JS Only

Tab Index Manager

Removes elements from tab order when a panel is hidden, restores on open.

Attribute
data-tab-hidden
JS JavaScript
document.querySelectorAll('[data-tab-hidden]').forEach(el => {
  const items = el.querySelectorAll('a,button,input,select,textarea,[tabindex]');
  const apply = () => {
    const hidden = !el.classList.contains('is-open');
    items.forEach(item => item.setAttribute('tabindex', hidden ? '-1' : '0'));
  };
  apply();
  new MutationObserver(apply).observe(el, { attributes: true, attributeFilter: ['class'] });
});
Accessibility
#131 JS Only

Autoplay Video on Scroll

Plays a video when it enters the viewport and pauses when it leaves.

Attribute
data-autoplay-scroll
JS JavaScript
document.querySelectorAll('video[data-autoplay-scroll]').forEach(video => {
  new IntersectionObserver(([e]) => {
    e.isIntersecting ? video.play() : video.pause();
  }, { threshold: 0.4 }).observe(video);
});
Media
#132 JS Only

Mute Toggle Button

Toggles mute on the nearest video element.

Attribute
data-mute-toggle
JS JavaScript
document.querySelectorAll('[data-mute-toggle]').forEach(btn => {
  const video = btn.closest('[data-video-wrap]')?.querySelector('video') || document.querySelector('video');
  if (!video) return;
  btn.addEventListener('click', () => {
    video.muted = !video.muted;
    btn.textContent = video.muted ? '🔇' : '🔊';
  });
});
Media
#133 CSS + JS

Image Colour Palette Extractor

Extracts the dominant colour from an image using canvas and sets it as a CSS var.

Attribute
data-extract-color
CSS
[data-extract-color] { --dominant: #888; }
JS JavaScript
document.querySelectorAll('img[data-extract-color]').forEach(img => {
  img.crossOrigin = 'anonymous';
  img.addEventListener('load', () => {
    const c = document.createElement('canvas');
    c.width = c.height = 1;
    c.getContext('2d').drawImage(img, 0, 0, 1, 1);
    const [r,g,b] = c.getContext('2d').getImageData(0,0,1,1).data;
    img.style.setProperty('--dominant', `rgb(${r},${g},${b})`);
  }, { once: true });
  if (img.complete) img.dispatchEvent(new Event('load'));
});
Media
#134 CSS + JS

Lightbox Image Viewer

Opens images in a full-screen overlay when clicked.

Attribute
data-lightbox
CSS
#wm-lightbox {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.9);
  z-index: 9999;
  display: none;
  align-items: center;
  justify-content: center;
  cursor: zoom-out;
}
#wm-lightbox.open { display: flex; }
#wm-lightbox img  { max-width: 90vw; max-height: 90vh; border-radius: 4px; }
JS JavaScript
let lb = document.getElementById('wm-lightbox');
if (!lb) {
  lb = document.createElement('div');
  lb.id = 'wm-lightbox';
  lb.innerHTML = '<img>';
  document.body.appendChild(lb);
  lb.addEventListener('click', () => lb.classList.remove('open'));
}
document.querySelectorAll('img[data-lightbox]').forEach(img => {
  img.style.cursor = 'zoom-in';
  img.addEventListener('click', () => {
    lb.querySelector('img').src = img.src;
    lb.classList.add('open');
  });
});
Media
#135 CSS + JS

Long Press Action

Fires a custom 'longpress' event after 500ms of holding down the mouse or touch.

Attribute
data-long-press
CSS
[data-long-press] { user-select: none; }
JS JavaScript
document.querySelectorAll('[data-long-press]').forEach(el => {
  let timer;
  const start = () => { timer = setTimeout(() => el.dispatchEvent(new CustomEvent('longpress', { bubbles: true })), 500); };
  const cancel = () => clearTimeout(timer);
  el.addEventListener('mousedown', start);
  el.addEventListener('touchstart', start, { passive: true });
  el.addEventListener('mouseup', cancel);
  el.addEventListener('touchend', cancel);
  el.addEventListener('longpress', () => console.log('Long press!'));
});
Interaction
#136 CSS + JS

Double Click Action

Fires a custom event on double click — useful for like/favourite interactions.

Attribute
data-dblclick-like
CSS
[data-dblclick-like] .like-pop {
  position: absolute;
  font-size: 2rem;
  pointer-events: none;
  animation: popUp 0.6s ease forwards;
}
@keyframes popUp {
  from { transform: scale(0) translateY(0); opacity: 1; }
  to   { transform: scale(1.5) translateY(-60px); opacity: 0; }
}
JS JavaScript
document.querySelectorAll('[data-dblclick-like]').forEach(el => {
  el.style.position = 'relative';
  el.addEventListener('dblclick', e => {
    const pop = document.createElement('span');
    pop.className = 'like-pop';
    pop.textContent = '❤️';
    pop.style.cssText = `left:${e.offsetX - 15}px;top:${e.offsetY - 15}px`;
    el.appendChild(pop);
    pop.addEventListener('animationend', () => pop.remove());
  });
});
Interaction
#137 JS Only

Swipe Left / Right Detection

Detects horizontal swipe gestures and fires 'swipe-left' or 'swipe-right' events.

Attribute
data-swipe
JS JavaScript
document.querySelectorAll('[data-swipe]').forEach(el => {
  let sx = 0, sy = 0;
  el.addEventListener('touchstart', e => { sx = e.touches[0].clientX; sy = e.touches[0].clientY; }, { passive: true });
  el.addEventListener('touchend', e => {
    const dx = e.changedTouches[0].clientX - sx;
    const dy = e.changedTouches[0].clientY - sy;
    if (Math.abs(dx) < 30 || Math.abs(dx) < Math.abs(dy)) return;
    el.dispatchEvent(new CustomEvent(dx > 0 ? 'swipe-right' : 'swipe-left', { bubbles: true }));
  });
  el.addEventListener('swipe-left',  () => console.log('Swiped left'));
  el.addEventListener('swipe-right', () => console.log('Swiped right'));
});
Interaction
#138 CSS + JS

Hover Intent (delayed hover)

Only triggers hover state after the mouse has stayed still for 200ms — prevents accidental triggers.

Attribute
data-hover-intent
CSS
[data-hover-intent] .intent-content {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
[data-hover-intent].hovered .intent-content {
  opacity: 1;
  pointer-events: auto;
}
JS JavaScript
document.querySelectorAll('[data-hover-intent]').forEach(el => {
  let timer;
  el.addEventListener('mouseenter', () => { timer = setTimeout(() => el.classList.add('hovered'), 200); });
  el.addEventListener('mouseleave', () => { clearTimeout(timer); el.classList.remove('hovered'); });
});
Interaction
#139 CSS + JS

Pinch Zoom Container

Enables pinch-to-zoom on a container using touch events.

Attribute
data-pinch-zoom
CSS
[data-pinch-zoom] { touch-action: none; overflow: hidden; }
[data-pinch-zoom] > * { transform-origin: center center; transition: transform 0.1s ease; }
JS JavaScript
document.querySelectorAll('[data-pinch-zoom]').forEach(el => {
  const inner = el.firstElementChild;
  let lastDist = 0, scale = 1;
  el.addEventListener('touchmove', e => {
    if (e.touches.length !== 2) return;
    e.preventDefault();
    const dx = e.touches[0].clientX - e.touches[1].clientX;
    const dy = e.touches[0].clientY - e.touches[1].clientY;
    const dist = Math.hypot(dx, dy);
    if (lastDist) scale = Math.max(1, Math.min(4, scale * (dist / lastDist)));
    inner.style.transform = `scale(${scale})`;
    lastDist = dist;
  }, { passive: false });
  el.addEventListener('touchend', () => { lastDist = 0; });
});
Interaction
#140 CSS + JS

Drag to Scroll

Click and drag to scroll a container horizontally — carousel without a library.

Attribute
data-drag-scroll
CSS
[data-drag-scroll] { cursor: grab; overflow-x: auto; scrollbar-width: none; }
[data-drag-scroll]::-webkit-scrollbar { display: none; }
[data-drag-scroll].active { cursor: grabbing; user-select: none; }
JS JavaScript
document.querySelectorAll('[data-drag-scroll]').forEach(el => {
  let down = false, startX, scrollLeft;
  el.addEventListener('mousedown', e => {
    down = true; el.classList.add('active');
    startX = e.pageX - el.offsetLeft;
    scrollLeft = el.scrollLeft;
  });
  document.addEventListener('mouseup', () => { down = false; el.classList.remove('active'); });
  el.addEventListener('mousemove', e => {
    if (!down) return;
    el.scrollLeft = scrollLeft - (e.pageX - el.offsetLeft - startX) * 1.5;
  });
});
Interaction
#141 JS Only

CSS Custom Property Setter

Reads a data attribute value and sets it as a CSS custom property.

Attribute
data-css-var="--accent:#6366f1"
JS JavaScript
document.querySelectorAll('[data-css-var]').forEach(el => {
  el.dataset.cssVar.split(';').forEach(pair => {
    const [prop, val] = pair.split(':');
    if (prop && val) el.style.setProperty(prop.trim(), val.trim());
  });
});
Color & Theme
#142 CSS + JS

Random Accent on Load

Picks a random accent colour from a palette on each page load.

Attribute
data-random-accent
CSS
[data-random-accent] { color: var(--accent-random, #6366f1); }
JS JavaScript
const palette = ['#6366f1','#ec4899','#f59e0b','#22c55e','#3b82f6','#ef4444','#8b5cf6'];
const pick = palette[Math.floor(Math.random() * palette.length)];
document.querySelectorAll('[data-random-accent]').forEach(el => {
  el.style.setProperty('--accent-random', pick);
});
Color & Theme
#143 JS Only

System Colour Scheme Class

Adds 'prefers-dark' or 'prefers-light' class to the root based on OS setting.

Attribute
data-sys-theme
JS JavaScript
const dark = window.matchMedia('(prefers-color-scheme: dark)');
document.documentElement.classList.toggle('prefers-dark', dark.matches);
document.documentElement.classList.toggle('prefers-light', !dark.matches);
dark.addEventListener('change', e => {
  document.documentElement.classList.toggle('prefers-dark', e.matches);
  document.documentElement.classList.toggle('prefers-light', !e.matches);
});
Color & Theme
#144 CSS + JS

Colour Swatch Picker

Clicking a swatch sets a CSS variable to that colour — live theme switching.

Attribute
data-swatch="#6366f1"
CSS
[data-swatch] {
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--sw);
  cursor: pointer;
  border: 2px solid transparent;
  transition: border-color 0.15s;
}
[data-swatch].active { border-color: var(--text); }
JS JavaScript
document.querySelectorAll('[data-swatch]').forEach(el => {
  el.style.setProperty('--sw', el.dataset.swatch);
  el.addEventListener('click', () => {
    document.documentElement.style.setProperty('--accent', el.dataset.swatch);
    document.querySelectorAll('[data-swatch]').forEach(s => s.classList.remove('active'));
    el.classList.add('active');
  });
});
Color & Theme
#145 CSS Only

Invert On Dark Mode

Inverts a specific element (e.g. a logo) only when dark mode is active.

Attribute
data-invert-dark
CSS
html[data-theme="dark"] [data-invert-dark] {
  filter: invert(1);
}
Color & Theme
#146 JS Only

Obfuscate Email

Builds a mailto link from data attributes to avoid email harvesting bots.

Attribute
data-email-user="hello" data-email-domain="site.com"
JS JavaScript
document.querySelectorAll('[data-email-user]').forEach(el => {
  const email = el.dataset.emailUser + '@' + el.dataset.emailDomain;
  el.href = 'mailto:' + email;
  if (!el.textContent.trim()) el.textContent = email;
});
Misc
#147 CSS + JS

Cookie Consent Banner

Shows a banner until the user accepts. Stores consent in localStorage.

Attribute
data-cookie-banner
CSS
[data-cookie-banner] {
  position: fixed; bottom: 1rem; left: 1rem; right: 1rem;
  max-width: 480px; margin: 0 auto;
  background: #111; color: #fff;
  border-radius: 12px; padding: 1rem 1.25rem;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  font-size: 0.83rem; z-index: 9999;
}
[data-cookie-banner].hidden { display: none; }
JS JavaScript
document.querySelectorAll('[data-cookie-banner]').forEach(el => {
  if (localStorage.getItem('cookie-consent')) { el.classList.add('hidden'); return; }
  el.querySelector('[data-accept]')?.addEventListener('click', () => {
    localStorage.setItem('cookie-consent', '1');
    el.classList.add('hidden');
  });
});
Misc
#148 CSS + JS

Survey NPS Widget

Renders a 0–10 NPS scale and logs the selected score.

Attribute
data-nps
CSS
[data-nps] { display: flex; gap: 4px; flex-wrap: wrap; }
[data-nps] button {
  width: 36px; height: 36px;
  border-radius: 50%; border: 1px solid var(--border-med);
  background: none; font-size: 0.8rem; cursor: pointer; color: var(--text);
  transition: background 0.15s, color 0.15s;
}
[data-nps] button.selected { background: var(--text); color: var(--bg); }
JS JavaScript
document.querySelectorAll('[data-nps]').forEach(el => {
  for (let i = 0; i <= 10; i++) {
    const b = document.createElement('button');
    b.textContent = String(i);
    b.addEventListener('click', () => {
      el.querySelectorAll('button').forEach(x => x.classList.remove('selected'));
      b.classList.add('selected');
      el.dispatchEvent(new CustomEvent('nps-score', { detail: i, bubbles: true }));
      console.log('NPS Score:', i);
    });
    el.appendChild(b);
  }
});
Misc
#149 CSS + JS

Emoji Reaction Bar

Adds a row of emoji reaction buttons with live increment counters.

Attribute
data-reactions="👍,❤️,🔥,🎉"
CSS
[data-reactions] { display: flex; gap: 0.4rem; flex-wrap: wrap; }
[data-reactions] button {
  display: flex; align-items: center; gap: 0.3rem;
  border: 1px solid var(--border); border-radius: 999px;
  background: none; padding: 0.3rem 0.75rem;
  font-size: 0.85rem; cursor: pointer; color: var(--text);
  transition: background 0.15s, border-color 0.15s;
}
[data-reactions] button:hover { background: var(--border); }
[data-reactions] button.reacted { border-color: var(--accent, #6366f1); background: rgba(99,102,241,0.1); }
JS JavaScript
document.querySelectorAll('[data-reactions]').forEach(el => {
  el.dataset.reactions.split(',').forEach(emoji => {
    let count = 0;
    const btn = document.createElement('button');
    const span = document.createElement('span');
    span.textContent = '0';
    btn.append(emoji, ' ', span);
    btn.addEventListener('click', () => {
      count = btn.classList.toggle('reacted') ? count + 1 : count - 1;
      span.textContent = String(count);
    });
    el.appendChild(btn);
  });
});
Misc
#150 JS Only

Festive Snow Effect

Generates falling snowflakes across the viewport — great for seasonal pages.

Attribute
data-snow
CSS
@keyframes snowFall {
  to { transform: translateY(110vh) rotate(360deg); opacity: 0; }
}
JS JavaScript
if (document.querySelector('[data-snow]')) {
  for (let i = 0; i < 60; i++) {
    const flake = document.createElement('span');
    const size = Math.random() * 10 + 4;
    Object.assign(flake.style, {
      position: 'fixed',
      top: '-10px',
      left: Math.random() * 100 + 'vw',
      width: size + 'px',
      height: size + 'px',
      borderRadius: '50%',
      background: '#fff',
      opacity: Math.random() * 0.7 + 0.3,
      pointerEvents: 'none',
      zIndex: 9998,
      animation: `snowFall ${Math.random() * 4 + 4}s linear ${Math.random() * 6}s infinite`,
    });
    document.body.appendChild(flake);
  }
}
Misc
#151 CSS Only

Zoom In on Scroll

Element scales from 80% to full size as it enters the viewport.

Attribute
data-animate="zoom-in"
CSS
[data-animate="zoom-in"]{opacity:0;transform:scale(0.8);animation:zoomIn linear both;animation-timeline:view();animation-range:entry 0% entry 25%;}@keyframes zoomIn{to{opacity:1;transform:scale(1);}}
Animations
#152 CSS Only

Flip In Y

Card flips in on the Y axis when scrolled into view.

Attribute
data-animate="flip-y"
CSS
[data-animate="flip-y"]{opacity:0;transform:perspective(600px) rotateY(90deg);animation:flipY linear both;animation-timeline:view();animation-range:entry 0% entry 30%;}@keyframes flipY{to{opacity:1;transform:perspective(600px) rotateY(0deg);}}
Animations
#153 CSS Only

Bounce In

Element bounces in with a spring overshoot when entering the viewport.

Attribute
data-animate="bounce-in"
CSS
[data-animate="bounce-in"]{opacity:0;transform:scale(0.3);animation:bounceIn 0.7s cubic-bezier(.36,.07,.19,.97) both;animation-timeline:view();animation-range:entry 0% entry 20%;}@keyframes bounceIn{60%{opacity:1;transform:scale(1.1);}80%{transform:scale(0.95);}to{opacity:1;transform:scale(1);}}
Animations
#154 CSS Only

Slide In Left

Element slides in from the left side of the screen.

Attribute
data-animate="slide-left"
CSS
[data-animate="slide-left"]{opacity:0;transform:translateX(-60px);animation:slideLeft linear both;animation-timeline:view();animation-range:entry 0% entry 25%;}@keyframes slideLeft{to{opacity:1;transform:none;}}
Animations
#155 CSS Only

Slide In Right

Element slides in from the right side of the screen.

Attribute
data-animate="slide-right"
CSS
[data-animate="slide-right"]{opacity:0;transform:translateX(60px);animation:slideRight linear both;animation-timeline:view();animation-range:entry 0% entry 25%;}@keyframes slideRight{to{opacity:1;transform:none;}}
Animations
#156 CSS Only

Blur In

Element transitions from blurry to sharp as it enters view.

Attribute
data-animate="blur-in"
CSS
[data-animate="blur-in"]{opacity:0;filter:blur(12px);animation:blurIn linear both;animation-timeline:view();animation-range:entry 0% entry 30%;}@keyframes blurIn{to{opacity:1;filter:blur(0);}}
Animations
#157 CSS Only

Rotate In

Element rotates from 45deg into its natural position on scroll.

Attribute
data-animate="rotate-in"
CSS
[data-animate="rotate-in"]{opacity:0;transform:rotate(45deg) scale(0.7);animation:rotateIn linear both;animation-timeline:view();animation-range:entry 0% entry 25%;}@keyframes rotateIn{to{opacity:1;transform:none;}}
Animations
#158 CSS Only

Skew In

Applies a skew distortion that resolves as the element enters the viewport.

Attribute
data-animate="skew-in"
CSS
[data-animate="skew-in"]{opacity:0;transform:skewX(-20deg);animation:skewIn linear both;animation-timeline:view();animation-range:entry 0% entry 25%;}@keyframes skewIn{to{opacity:1;transform:skewX(0);}}
Animations
#159 CSS Only

Typewriter Cursor

Adds a blinking cursor after any element to mimic a typewriter.

Attribute
data-cursor
CSS
[data-cursor]::after{content:'|';animation:blink 0.8s step-end infinite;}@keyframes blink{50%{opacity:0;}}
Animations
#160 CSS Only

Heartbeat Pulse

Element pulses with a heartbeat rhythm to draw attention.

Attribute
data-heartbeat
CSS
[data-heartbeat]{animation:heartbeat 1.2s ease-in-out infinite;}@keyframes heartbeat{0%,100%{transform:scale(1);}14%{transform:scale(1.12);}28%{transform:scale(1);}42%{transform:scale(1.08);}70%{transform:scale(1);}}
Animations
#161 CSS Only

Sticky Progress Bar

A top progress bar fills as the user scrolls down the page.

Attribute
data-scroll-progress
CSS
[data-scroll-progress]{position:fixed;top:0;left:0;height:3px;background:linear-gradient(90deg,#6366f1,#ec4899);animation:scrollProgress linear both;animation-timeline:scroll(root);animation-range:0% 100%;z-index:9999;width:100%;}@keyframes scrollProgress{from{transform:scaleX(0);}to{transform:scaleX(1);}}
Scroll
#162 CSS Only

Section Snap Scroll

Sections snap smoothly one at a time when the user scrolls.

Attribute
data-snap-scroll
CSS
[data-snap-scroll]{scroll-snap-type:y mandatory;overflow-y:scroll;height:100vh;}[data-snap-scroll]>*{scroll-snap-align:start;height:100vh;}
Scroll
#163 CSS Only

Horizontal Scroll Section

Scrolls content horizontally inside a fixed vertical scroll container.

Attribute
data-hscroll
CSS
[data-hscroll]{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;}[data-hscroll]::-webkit-scrollbar{display:none;}[data-hscroll]>*{scroll-snap-align:start;min-width:100vw;}
Scroll
#164 CSS Only

Scroll-Driven Rotate

An element continuously rotates as the user scrolls the page.

Attribute
data-scroll-rotate
CSS
[data-scroll-rotate]{animation:spinOnScroll linear both;animation-timeline:scroll(root);}@keyframes spinOnScroll{to{transform:rotate(360deg);}}
Scroll
#165 CSS Only

Shrink on Scroll

An element (e.g. hero title) shrinks as the user scrolls away from it.

Attribute
data-shrink
CSS
[data-shrink]{animation:shrinkScroll linear both;animation-timeline:view();animation-range:exit 0% exit 50%;}@keyframes shrinkScroll{to{transform:scale(0.6);opacity:0;}}
Scroll
#166 JS Only

Sticky Section Counter

Tracks which section is currently in view and updates a counter element.

Attribute
data-section-track
JS JavaScript
const sections=document.querySelectorAll('[data-section-track]');const counter=document.getElementById('section-count');const obs=new IntersectionObserver(es=>es.forEach((e,i)=>{if(e.isIntersecting&&counter)counter.textContent=String(i+1);}),{threshold:0.5});sections.forEach(s=>obs.observe(s));
Scroll
#167 CSS + JS

Back to Top Button

Shows a back-to-top button after scrolling 300px and hides it at top.

Attribute
data-back-top
CSS
[data-back-top]{position:fixed;bottom:2rem;right:2rem;opacity:0;pointer-events:none;transition:opacity 0.3s;}[data-back-top].show{opacity:1;pointer-events:auto;}
JS JavaScript
const btn=document.querySelector('[data-back-top]');window.addEventListener('scroll',()=>btn?.classList.toggle('show',scrollY>300));btn?.addEventListener('click',()=>scrollTo({top:0,behavior:'smooth'}));
Scroll
#168 JS Only

Infinite Scroll Loader

Fires a callback when the user nears the bottom of the page to load more content.

Attribute
data-infinite-scroll
JS JavaScript
const sentinel=document.querySelector('[data-infinite-scroll]');const obs=new IntersectionObserver(es=>{if(es[0].isIntersecting)sentinel?.dispatchEvent(new CustomEvent('load-more'));});if(sentinel)obs.observe(sentinel);
Scroll
#169 CSS Only

Scroll-Linked Color Change

Background color interpolates from one color to another as the page is scrolled.

Attribute
data-scroll-color
CSS
[data-scroll-color]{animation:bgColorScroll linear both;animation-timeline:scroll(root);}@keyframes bgColorScroll{from{background-color:#6366f1;}to{background-color:#ec4899;}}
Scroll
#170 JS Only

Lazy Background Image

Applies a background image to an element only when it enters the viewport.

Attribute
data-lazy-bg
JS JavaScript
const obs=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){const el=e.target;el.style.backgroundImage=`url(${el.dataset.lazyBg})`;obs.unobserve(el);}}));document.querySelectorAll('[data-lazy-bg]').forEach(el=>obs.observe(el));
Scroll
#171 JS Only

Magnetic Button

Button follows the cursor slightly when hovered, creating a magnetic pull effect.

Attribute
data-magnetic
JS JavaScript
document.querySelectorAll('[data-magnetic]').forEach(el=>{el.addEventListener('mousemove',e=>{const r=el.getBoundingClientRect();const x=(e.clientX-r.left-r.width/2)*0.3;const y=(e.clientY-r.top-r.height/2)*0.3;el.style.transform=`translate(${x}px,${y}px)`;});el.addEventListener('mouseleave',()=>el.style.transform='');});
Cursor
#172 CSS + JS

Custom Cursor Dot

Replaces the default cursor with a small colored dot that follows the mouse.

Attribute
data-cursor-dot
CSS
body{cursor:none;}[data-cursor-dot]{width:12px;height:12px;background:#6366f1;border-radius:50%;position:fixed;pointer-events:none;z-index:9999;transform:translate(-50%,-50%);transition:transform 0.1s;}
JS JavaScript
const dot=document.querySelector('[data-cursor-dot]');document.addEventListener('mousemove',e=>{if(dot){dot.style.left=e.clientX+'px';dot.style.top=e.clientY+'px';}});
Cursor
#173 CSS + JS

Cursor Trail

Creates a trailing particle effect that follows the mouse cursor.

Attribute
data-cursor-trail
CSS
[data-cursor-trail-particle]{width:8px;height:8px;border-radius:50%;background:#6366f1;position:fixed;pointer-events:none;z-index:9998;opacity:0;transition:opacity 0.5s;}
JS JavaScript
document.querySelector('[data-cursor-trail]')&&document.addEventListener('mousemove',e=>{const p=document.createElement('div');p.className='data-cursor-trail-particle';Object.assign(p.style,{left:e.clientX+'px',top:e.clientY+'px',width:'8px',height:'8px',borderRadius:'50%',background:'#6366f1',position:'fixed',pointerEvents:'none',zIndex:'9998',transition:'opacity 0.6s, transform 0.6s'});document.body.appendChild(p);setTimeout(()=>{p.style.opacity='1';p.style.transform='scale(0)';},10);setTimeout(()=>p.remove(),700);});
Cursor
#174 CSS + JS

Spotlight Cursor

Creates a radial spotlight that follows the cursor, revealing content beneath it.

Attribute
data-spotlight
CSS
[data-spotlight]{background:radial-gradient(circle 150px at var(--mx,50%) var(--my,50%),rgba(255,255,255,0.08) 0%,transparent 100%);transition:background 0.1s;}
JS JavaScript
document.querySelectorAll('[data-spotlight]').forEach(el=>{el.addEventListener('mousemove',e=>{const r=el.getBoundingClientRect();el.style.setProperty('--mx',e.clientX-r.left+'px');el.style.setProperty('--my',e.clientY-r.top+'px');});});
Cursor
#175 CSS Only

Grow on Hover

Cursor grows into a large circle when hovering over a link or button.

Attribute
data-cursor-grow
CSS
[data-cursor-grow]:hover~[data-cursor-dot]{transform:translate(-50%,-50%) scale(4);opacity:0.4;}
Cursor
#176 CSS Only

Gradient Text

Text fills with a vivid gradient using background-clip trick.

Attribute
data-gradient-text
CSS
[data-gradient-text]{background:linear-gradient(135deg,#6366f1,#ec4899);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;}
Text
#177 CSS Only

Text Stroke Outline

Adds a colored outline stroke to text without filling it.

Attribute
data-text-stroke
CSS
[data-text-stroke]{-webkit-text-stroke:2px #6366f1;color:transparent;font-weight:900;}
Text
#178 CSS Only

Neon Glow Text

Text glows with a neon light halo effect.

Attribute
data-neon
CSS
[data-neon]{color:#6366f1;text-shadow:0 0 7px #6366f1,0 0 20px #6366f1,0 0 40px #6366f1;}
Text
#179 CSS Only

Text Reveal Mask

Text slides up from behind a mask clip, revealing it character by character.

Attribute
data-text-reveal
CSS
[data-text-reveal]{overflow:hidden;}[data-text-reveal] span{display:inline-block;transform:translateY(110%);animation:textReveal 0.6s cubic-bezier(.16,1,.3,1) forwards;}@keyframes textReveal{to{transform:translateY(0);}}
Text
#180 JS Only

Scramble Text on Hover

Text characters shuffle randomly then resolve to their correct values on hover.

Attribute
data-scramble
JS JavaScript
document.querySelectorAll('[data-scramble]').forEach(el=>{const orig=el.textContent??'';const chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';el.addEventListener('mouseenter',()=>{let iter=0;const id=setInterval(()=>{el.textContent=orig.split('').map((c,i)=>i<iter?c:chars[Math.floor(Math.random()*chars.length)]).join('');if(++iter>orig.length)clearInterval(id);},40);});});
Text
#181 CSS + JS

Split Word Animation

Splits text into individual words, each animating independently.

Attribute
data-split-words
CSS
[data-split-words] span{display:inline-block;opacity:0;transform:translateY(20px);transition:opacity 0.5s,transform 0.5s;}[data-split-words].is-visible span{opacity:1;transform:none;}
JS JavaScript
document.querySelectorAll('[data-split-words]').forEach(el=>{el.innerHTML=el.textContent?.split(' ').map((w,i)=>`<span style="transition-delay:${i*0.06}s">${w}&nbsp;</span>`).join('')??'';const obs=new IntersectionObserver(([e])=>{if(e.isIntersecting){el.classList.add('is-visible');obs.disconnect();}});obs.observe(el);});
Text
#182 CSS Only

Marquee Scroll Text

Text scrolls infinitely like a news ticker.

Attribute
data-marquee
CSS
[data-marquee]{overflow:hidden;white-space:nowrap;}[data-marquee] span{display:inline-block;animation:marquee 10s linear infinite;}@keyframes marquee{from{transform:translateX(100%);}to{transform:translateX(-100%);}}
Text
#183 CSS Only

Letter Spacing on Hover

Text expands its letter spacing on hover for a cinematic reveal.

Attribute
data-ls-hover
CSS
[data-ls-hover]{letter-spacing:0;transition:letter-spacing 0.4s ease;}[data-ls-hover]:hover{letter-spacing:0.3em;}
Text
#184 CSS Only

Glitch Effect

Text flickers with a RGB glitch distortion on hover.

Attribute
data-glitch
CSS
[data-glitch]{position:relative;}[data-glitch]:hover{animation:glitch 0.4s steps(2) infinite;}@keyframes glitch{0%{text-shadow:2px 0 red,-2px 0 blue;}50%{text-shadow:-2px 0 red,2px 0 blue;}100%{text-shadow:2px 0 red,-2px 0 blue;}}
Text
#185 JS Only

Auto Typing Effect

Simulates a typewriter that types, pauses, then deletes text in a loop.

Attribute
data-typewriter
JS JavaScript
document.querySelectorAll('[data-typewriter]').forEach(el=>{const words=(el.dataset.typewriter||'Hello,World,Webflow').split(',');let wi=0,ci=0,del=false;setInterval(()=>{const w=words[wi];if(!del){el.textContent=w.slice(0,++ci);if(ci===w.length)del=true;}else{el.textContent=w.slice(0,--ci);if(ci===0){del=false;wi=(wi+1)%words.length;}}},120);});
Text
#186 CSS Only

Frosted Glass Card

Card with frosted glass effect using backdrop-filter blur.

Attribute
data-glass
CSS
[data-glass]{background:rgba(255,255,255,0.08);backdrop-filter:blur(16px) saturate(160%);-webkit-backdrop-filter:blur(16px) saturate(160%);border:1px solid rgba(255,255,255,0.12);border-radius:16px;}
Visual FX
#187 CSS Only

Neumorphism Card

Soft UI neumorphic shadow card with inset/outset shadow.

Attribute
data-neumorphic
CSS
[data-neumorphic]{background:#e0e5ec;border-radius:16px;box-shadow:6px 6px 12px #b8bec7,-6px -6px 12px #ffffff;}
Visual FX
#188 CSS Only

Grain Texture Overlay

Adds a subtle SVG grain noise overlay to any element.

Attribute
data-grain
CSS
[data-grain]{position:relative;}[data-grain]::after{content:'';position:absolute;inset:0;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");pointer-events:none;border-radius:inherit;}
Visual FX
#189 CSS Only

Holo Foil Effect

Iridescent holographic foil sheen that shifts color with mouse movement.

Attribute
data-holo
CSS
[data-holo]{background:linear-gradient(135deg,#6366f1,#ec4899,#f59e0b,#22c55e);background-size:400% 400%;animation:holoShift 4s ease infinite;}@keyframes holoShift{0%,100%{background-position:0% 50%;}50%{background-position:100% 50%;}}
Visual FX
#190 CSS Only

Animated Gradient Border

Border glows with an animated gradient that rotates around the element.

Attribute
data-grad-border
CSS
[data-grad-border]{position:relative;border-radius:12px;}[data-grad-border]::before{content:'';position:absolute;inset:-2px;border-radius:inherit;background:conic-gradient(#6366f1,#ec4899,#f59e0b,#22c55e,#6366f1);z-index:-1;animation:rotateBorder 3s linear infinite;}@keyframes rotateBorder{to{transform:rotate(360deg);}}
Visual FX
#191 CSS + JS

3D Tilt Card

Card tilts in 3D perspective to follow the mouse cursor on hover.

Attribute
data-tilt
CSS
[data-tilt]{transform-style:preserve-3d;transition:transform 0.1s;}
JS JavaScript
document.querySelectorAll('[data-tilt]').forEach(el=>{el.addEventListener('mousemove',e=>{const r=el.getBoundingClientRect();const x=((e.clientX-r.left)/r.width-0.5)*20;const y=((e.clientY-r.top)/r.height-0.5)*-20;el.style.transform=`perspective(600px) rotateX(${y}deg) rotateY(${x}deg)`;});el.addEventListener('mouseleave',()=>el.style.transform='');});
Visual FX
#192 JS Only

Confetti Burst

Fires a burst of colorful confetti particles from an element on click.

Attribute
data-confetti
JS JavaScript
document.querySelectorAll('[data-confetti]').forEach(el=>{el.addEventListener('click',e=>{for(let i=0;i<40;i++){const p=document.createElement('span');const angle=Math.random()*360;const dist=Math.random()*80+40;const colors=['#6366f1','#ec4899','#f59e0b','#22c55e','#ef4444'];Object.assign(p.style,{position:'fixed',left:e.clientX+'px',top:e.clientY+'px',width:'8px',height:'8px',borderRadius:'2px',background:colors[Math.floor(Math.random()*colors.length)],pointerEvents:'none',zIndex:9999,transition:'all 0.7s ease-out',transform:`rotate(${angle}deg)`});document.body.appendChild(p);setTimeout(()=>{p.style.transform=`translate(${Math.cos(angle*Math.PI/180)*dist}px,${Math.sin(angle*Math.PI/180)*dist}px) scale(0)`;p.style.opacity='0';},10);setTimeout(()=>p.remove(),800);}});});
Visual FX
#193 CSS Only

Liquid Button

Button fill bubbles up from the bottom like a liquid on hover.

Attribute
data-liquid
CSS
[data-liquid]{position:relative;overflow:hidden;}[data-liquid]::after{content:'';position:absolute;bottom:-100%;left:0;width:100%;height:100%;background:#6366f1;border-radius:50% 50% 0 0;transition:bottom 0.4s ease;}[data-liquid]:hover::after{bottom:0;border-radius:0;}
Visual FX
#194 CSS Only

RGB Split on Hover

Applies a color channel split (chromatic aberration) effect on hover.

Attribute
data-rgb-split
CSS
[data-rgb-split]:hover{text-shadow:2px 0 0 red,-2px 0 0 blue;filter:contrast(1.2);}
Visual FX
#195 CSS Only

Morphing Blob

A continuously morphing blob shape using border-radius animation.

Attribute
data-blob
CSS
[data-blob]{border-radius:60% 40% 30% 70%/60% 30% 70% 40%;animation:morphBlob 8s ease-in-out infinite;}@keyframes morphBlob{0%,100%{border-radius:60% 40% 30% 70%/60% 30% 70% 40%;}25%{border-radius:30% 60% 70% 40%/50% 60% 30% 60%;}50%{border-radius:50% 60% 30% 40%/40% 40% 60% 50%;}75%{border-radius:40% 60% 50% 30%/60% 40% 60% 40%;}}
Visual FX
#196 CSS Only

Skeleton Loading Card

Animated skeleton placeholder shimmer for loading states.

Attribute
data-skeleton
CSS
[data-skeleton]{background:linear-gradient(90deg,#f0f0f0 25%,#e0e0e0 50%,#f0f0f0 75%);background-size:200% 100%;animation:shimmer 1.5s infinite;}@keyframes shimmer{to{background-position:-200% 0;}}
UI Patterns
#197 CSS Only

Tooltip on Hover

Shows a custom tooltip above any element using only CSS.

Attribute
data-tooltip="Your text"
CSS
[data-tooltip]{position:relative;}[data-tooltip]::before{content:attr(data-tooltip);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:#1a1a2e;color:#fff;padding:0.3em 0.7em;border-radius:6px;font-size:0.75rem;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.2s;}[data-tooltip]:hover::before{opacity:1;}
UI Patterns
#198 CSS + JS

Ripple Click Effect

Material-style ripple radiates out from the click point.

Attribute
data-ripple
CSS
[data-ripple]{position:relative;overflow:hidden;}.ripple-wave{position:absolute;border-radius:50%;background:rgba(255,255,255,0.35);transform:scale(0);animation:rippleAnim 0.6s linear;pointer-events:none;}@keyframes rippleAnim{to{transform:scale(4);opacity:0;}}
JS JavaScript
document.querySelectorAll('[data-ripple]').forEach(el=>{el.addEventListener('click',e=>{const r=el.getBoundingClientRect();const w=document.createElement('span');const size=Math.max(r.width,r.height);w.className='ripple-wave';Object.assign(w.style,{width:size+'px',height:size+'px',left:e.clientX-r.left-size/2+'px',top:e.clientY-r.top-size/2+'px'});el.appendChild(w);setTimeout(()=>w.remove(),700);});});
UI Patterns
#199 CSS Only

Accordion Toggle

Pure CSS accordion — clicking a summary expands its content panel.

Attribute
data-accordion
CSS
[data-accordion] details{border-bottom:1px solid rgba(255,255,255,0.08);}[data-accordion] summary{cursor:pointer;list-style:none;padding:1rem;display:flex;justify-content:space-between;}[data-accordion] summary::after{content:'+';}[data-accordion] details[open] summary::after{content:'-';}
UI Patterns
#200 CSS + JS

Drawer Sidebar

Slides in a fixed sidebar drawer from the left on button click.

Attribute
data-drawer
CSS
[data-drawer]{position:fixed;top:0;left:0;height:100%;width:280px;background:#111;transform:translateX(-100%);transition:transform 0.35s cubic-bezier(.4,0,.2,1);z-index:1000;}[data-drawer].open{transform:none;}
JS JavaScript
document.querySelectorAll('[data-drawer-trigger]').forEach(btn=>{const id=btn.dataset.drawerTrigger;const drawer=document.getElementById(id);btn.addEventListener('click',()=>drawer?.classList.toggle('open'));});
UI Patterns
#201 CSS Only

Card Flip

Card flips to reveal its back face on hover using CSS 3D transforms.

Attribute
data-flip-card
CSS
[data-flip-card]{perspective:800px;}[data-flip-card] .front,[data-flip-card] .back{backface-visibility:hidden;transition:transform 0.6s;}[data-flip-card] .back{transform:rotateY(180deg);}[data-flip-card]:hover .front{transform:rotateY(-180deg);}[data-flip-card]:hover .back{transform:rotateY(0);}
UI Patterns
#202 CSS Only

Tabs Component

Pure CSS tabs with active state toggled via hidden radio inputs.

Attribute
data-tabs
CSS
[data-tabs] input[type=radio]{display:none;}[data-tabs] label{cursor:pointer;padding:0.5rem 1rem;border-bottom:2px solid transparent;}[data-tabs] input:checked+label{border-bottom-color:#6366f1;color:#6366f1;}
UI Patterns
#203 CSS Only

Rating Stars

Hover over stars to highlight them left-to-right using CSS sibling selectors.

Attribute
data-stars
CSS
[data-stars]{display:flex;flex-direction:row-reverse;gap:4px;}[data-stars] span{font-size:1.5rem;cursor:pointer;color:#555;transition:color 0.15s;}[data-stars] span:hover,[data-stars] span:hover~span{color:#f59e0b;}
UI Patterns
#204 CSS Only

Badge Counter

Shows a notification badge number on top-right of an icon or button.

Attribute
data-badge="3"
CSS
[data-badge]{position:relative;}[data-badge]::after{content:attr(data-badge);position:absolute;top:-6px;right:-6px;background:#ef4444;color:#fff;border-radius:50%;width:18px;height:18px;font-size:0.65rem;display:flex;align-items:center;justify-content:center;}
UI Patterns
#205 CSS Only

Keyboard Shortcut Badge

Displays a keyboard shortcut hint badge next to menu items.

Attribute
data-kbd="⌘K"
CSS
[data-kbd]::after{content:attr(data-kbd);background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.14);border-radius:5px;padding:0.1em 0.45em;font-size:0.72rem;font-family:monospace;margin-left:0.5em;color:rgba(255,255,255,0.5);}
UI Patterns
#206 CSS Only

CSS Masonry Grid

Creates a masonry-style column layout without JS using CSS columns.

Attribute
data-masonry
CSS
[data-masonry]{columns:3 220px;gap:1rem;}[data-masonry]>*{break-inside:avoid;margin-bottom:1rem;}
Layout
#207 CSS Only

Fluid Type Scale

Font size scales fluidly between min and max based on viewport width.

Attribute
data-fluid-type
CSS
[data-fluid-type]{font-size:clamp(1rem,2vw + 0.5rem,2.5rem);}
Layout
#208 CSS Only

Full-Bleed Section

Section breaks out of a container to fill the full viewport width.

Attribute
data-full-bleed
CSS
[data-full-bleed]{width:100vw;margin-left:50%;transform:translateX(-50%);}
Layout
#209 CSS Only

Center Absolutely

Absolutely centers any element in its relative parent.

Attribute
data-center
CSS
[data-center]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}
Layout
#210 CSS Only

Aspect Ratio Box

Locks any element to a specific aspect ratio (default 16:9).

Attribute
data-aspect="16/9"
CSS
[data-aspect]{aspect-ratio:attr(data-aspect number,16/9);}[data-aspect="1/1"]{aspect-ratio:1;}[data-aspect="4/3"]{aspect-ratio:4/3;}[data-aspect="16/9"]{aspect-ratio:16/9;}
Layout
#211 CSS Only

Equal Height Columns

Forces all direct children to share the same height automatically.

Attribute
data-equal-height
CSS
[data-equal-height]{display:flex;align-items:stretch;}[data-equal-height]>*{flex:1;}
Layout
#212 CSS Only

Sticky Sidebar

A sidebar that sticks to the top as you scroll past it.

Attribute
data-sticky-side
CSS
[data-sticky-side]{position:sticky;top:1rem;align-self:start;}
Layout
#213 CSS Only

Auto-fill Grid

Responsive grid that auto-fills columns based on min card width.

Attribute
data-auto-grid
CSS
[data-auto-grid]{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(260px,100%),1fr));gap:1.5rem;}
Layout
#214 CSS Only

Overlap Stack

Layers children on top of each other in a single grid cell.

Attribute
data-overlap
CSS
[data-overlap]{display:grid;}[data-overlap]>*{grid-area:1/1;}
Layout
#215 CSS Only

Split Screen

Splits the viewport into two equal vertical panels.

Attribute
data-split-screen
CSS
[data-split-screen]{display:grid;grid-template-columns:1fr 1fr;min-height:100vh;}@media(max-width:768px){[data-split-screen]{grid-template-columns:1fr;}}
Layout
#216 CSS Only

Floating Label Input

Label floats above the input field when focused or filled.

Attribute
data-float-label
CSS
[data-float-label]{position:relative;}[data-float-label] label{position:absolute;top:1rem;left:0.75rem;transition:all 0.2s;pointer-events:none;color:#888;}[data-float-label]:focus-within label,[data-float-label] input:not(:placeholder-shown)+label{top:-0.6rem;left:0.5rem;font-size:0.75rem;color:#6366f1;background:#fff;padding:0 0.25rem;}
Forms
#217 CSS + JS

Password Strength Meter

Shows a color-coded strength bar as the user types a password.

Attribute
data-pw-strength
CSS
[data-pw-strength-bar]{height:4px;border-radius:2px;transition:width 0.3s,background 0.3s;}
JS JavaScript
document.querySelectorAll('[data-pw-strength]').forEach(input=>{const bar=input.parentElement?.querySelector('[data-pw-strength-bar]');if(!bar)return;input.addEventListener('input',()=>{const v=input.value;let s=0;if(v.length>7)s++;if(/[A-Z]/.test(v))s++;if(/[0-9]/.test(v))s++;if(/[^A-Za-z0-9]/.test(v))s++;const colors=['#ef4444','#f59e0b','#22c55e','#6366f1'];bar.style.width=((s/4)*100)+'%';bar.style.background=colors[s-1]||'#ef4444';});});
Forms
#218 CSS + JS

Character Counter

Shows remaining character count beneath a textarea in real-time.

Attribute
data-char-count
CSS
[data-char-count-display]{font-size:0.75rem;color:#888;text-align:right;}
JS JavaScript
document.querySelectorAll('[data-char-count]').forEach(el=>{const max=parseInt(el.dataset.charCount)||200;const disp=document.createElement('span');disp.setAttribute('data-char-count-display','');disp.textContent=`0/${max}`;el.after(disp);el.addEventListener('input',()=>disp.textContent=`${el.value.length}/${max}`);});
Forms
#219 CSS + JS

Auto Resize Textarea

Textarea grows vertically as the user types more content.

Attribute
data-auto-resize
CSS
[data-auto-resize]{resize:none;overflow:hidden;}
JS JavaScript
document.querySelectorAll('[data-auto-resize]').forEach(el=>{el.addEventListener('input',()=>{el.style.height='auto';el.style.height=el.scrollHeight+'px';});});
Forms
#220 JS Only

Phone Mask Input

Auto-formats phone input with dashes as the user types.

Attribute
data-phone-mask
JS JavaScript
document.querySelectorAll('[data-phone-mask]').forEach(el=>{el.addEventListener('input',()=>{let v=el.value.replace(/D/g,'').slice(0,10);if(v.length>6)v=v.slice(0,3)+'-'+v.slice(3,6)+'-'+v.slice(6);else if(v.length>3)v=v.slice(0,3)+'-'+v.slice(3);el.value=v;});});
Forms
#221 CSS + JS

Drag & Drop File Zone

Highlights a drop zone and captures files dragged onto it.

Attribute
data-drop-zone
CSS
[data-drop-zone]{border:2px dashed rgba(99,102,241,0.3);border-radius:12px;transition:border-color 0.2s,background 0.2s;}[data-drop-zone].drag-over{border-color:#6366f1;background:rgba(99,102,241,0.06);}
JS JavaScript
document.querySelectorAll('[data-drop-zone]').forEach(zone=>{zone.addEventListener('dragover',e=>{e.preventDefault();zone.classList.add('drag-over');});zone.addEventListener('dragleave',()=>zone.classList.remove('drag-over'));zone.addEventListener('drop',e=>{e.preventDefault();zone.classList.remove('drag-over');zone.dispatchEvent(new CustomEvent('files-dropped',{detail:e.dataTransfer?.files}));});});
Forms
#222 CSS Only

Toggle Switch

Styled CSS-only toggle switch using a checkbox.

Attribute
data-toggle
CSS
[data-toggle]{appearance:none;width:44px;height:24px;background:#ccc;border-radius:12px;cursor:pointer;transition:background 0.3s;position:relative;}[data-toggle]::after{content:'';position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff;transition:transform 0.3s;}[data-toggle]:checked{background:#6366f1;}[data-toggle]:checked::after{transform:translateX(20px);}
Forms
#223 JS Only

Smooth Scroll to Anchor

Smooth-scrolls to any anchor target on click.

Attribute
data-smooth-scroll
JS JavaScript
document.querySelectorAll('[data-smooth-scroll]').forEach(a=>{a.addEventListener('click',e=>{const id=a.getAttribute('href');if(id?.startsWith('#')){e.preventDefault();document.querySelector(id)?.scrollIntoView({behavior:'smooth'});}});});
Navigation
#224 CSS + JS

Active Nav Highlight

Automatically marks the nav link matching the current URL as active.

Attribute
data-nav-active
CSS
[data-nav-active].active{color:#6366f1;font-weight:700;}
JS JavaScript
document.querySelectorAll('[data-nav-active]').forEach(a=>{if(a.getAttribute('href')===location.pathname)a.classList.add('active');});
Navigation
#225 CSS Only

Mega Menu on Hover

Shows a mega dropdown menu when hovering over a parent nav item.

Attribute
data-mega
CSS
[data-mega] .mega-menu{visibility:hidden;opacity:0;transform:translateY(10px);transition:all 0.25s;position:absolute;top:100%;left:0;}[data-mega]:hover .mega-menu{visibility:visible;opacity:1;transform:none;}
Navigation
#226 CSS Only

Breadcrumb Auto-Separator

Inserts › separators between breadcrumb items using CSS.

Attribute
data-breadcrumb
CSS
[data-breadcrumb] a+a::before{content:'›';margin:0 0.4em;color:#888;}
Navigation
#227 CSS + JS

Floating Nav on Scroll

Nav bar shrinks and gains a background blur after scrolling 60px.

Attribute
data-floatnav
CSS
[data-floatnav]{transition:padding 0.3s,backdrop-filter 0.3s;}[data-floatnav].scrolled{padding-top:0.5rem;padding-bottom:0.5rem;backdrop-filter:blur(12px);background:rgba(0,0,0,0.6);}
JS JavaScript
const nav=document.querySelector('[data-floatnav]');window.addEventListener('scroll',()=>nav?.classList.toggle('scrolled',scrollY>60));
Navigation
#228 CSS Only

Skip to Content Link

Accessibility-focused skip link that becomes visible on keyboard focus.

Attribute
data-skip-link
CSS
[data-skip-link]{position:absolute;top:-40px;left:0;transition:top 0.2s;}[data-skip-link]:focus{top:0;}
Navigation
#229 JS Only

Dark Mode Toggle

Toggles a dark class on <html> and persists choice to localStorage.

Attribute
data-dark-toggle
JS JavaScript
const btn=document.querySelector('[data-dark-toggle]');btn?.addEventListener('click',()=>{document.documentElement.classList.toggle('dark');localStorage.setItem('theme',document.documentElement.classList.contains('dark')?'dark':'light');});if(localStorage.getItem('theme')==='dark')document.documentElement.classList.add('dark');
Utility
#230 JS Only

Clipboard Copy Text

Copies the element's textContent or a data attribute value to clipboard.

Attribute
data-copy
JS JavaScript
document.querySelectorAll('[data-copy]').forEach(el=>{el.style.cursor='pointer';el.addEventListener('click',()=>{navigator.clipboard.writeText(el.dataset.copy||el.textContent||'').then(()=>{el.dataset.origText=el.textContent??'';el.textContent='Copied!';setTimeout(()=>el.textContent=el.dataset.origText,2000);});});});
Utility
#231 CSS + JS

Print Section

Prints only the marked section, hiding everything else.

Attribute
data-print-section
CSS
@media print{body>*:not([data-print-section]){display:none!important;}}
JS JavaScript
document.querySelector('[data-print-trigger]')?.addEventListener('click',()=>window.print());
Utility
#232 CSS + JS

Viewport Size Debug

Overlays current viewport width×height in corner during development.

Attribute
data-viewport-debug
CSS
[data-viewport-debug]{position:fixed;bottom:8px;left:8px;font-size:0.7rem;background:rgba(0,0,0,0.6);color:#fff;padding:4px 8px;border-radius:4px;pointer-events:none;z-index:9999;font-family:monospace;}
JS JavaScript
const vd=document.querySelector('[data-viewport-debug]');const upd=()=>{if(vd)vd.textContent=`${window.innerWidth}×${window.innerHeight}`;};upd();window.addEventListener('resize',upd);
Utility
#233 JS Only

Debounce Input

Fires an event only after the user stops typing for a set delay.

Attribute
data-debounce="400"
JS JavaScript
document.querySelectorAll('[data-debounce]').forEach(el=>{let t:any;const delay=parseInt(el.dataset.debounce)||400;el.addEventListener('input',()=>{clearTimeout(t);t=setTimeout(()=>el.dispatchEvent(new Event('debounced')),delay);});});
Utility
#234 CSS + JS

Drag to Scroll Container

Enables click-and-drag scrolling inside a container.

Attribute
data-drag-scroll
CSS
[data-drag-scroll]{cursor:grab;overflow:auto;user-select:none;}[data-drag-scroll].dragging{cursor:grabbing;}
JS JavaScript
document.querySelectorAll('[data-drag-scroll]').forEach(el=>{let down=false,sx=0,sl=0;el.addEventListener('mousedown',e=>{down=true;sx=e.pageX;sl=el.scrollLeft;el.classList.add('dragging');});document.addEventListener('mouseup',()=>{down=false;el.classList.remove('dragging');});el.addEventListener('mousemove',e=>{if(!down)return;el.scrollLeft=sl-(e.pageX-sx);});});
Utility
#235 CSS + JS

Lazy Load Image

Images only load when they enter the viewport using native loading=lazy.

Attribute
data-lazy-img
CSS
[data-lazy-img]{opacity:0;transition:opacity 0.4s;}[data-lazy-img].loaded{opacity:1;}
JS JavaScript
document.querySelectorAll('[data-lazy-img]').forEach(img=>{img.setAttribute('loading','lazy');img.addEventListener('load',()=>img.classList.add('loaded'));if(img.complete)img.classList.add('loaded');});
Performance
#236 JS Only

Defer Off-Screen Video

Pauses videos not in the viewport and plays them when visible.

Attribute
data-lazy-video
JS JavaScript
const obs=new IntersectionObserver(es=>es.forEach(e=>{const v=e.target as HTMLVideoElement;e.isIntersecting?v.play():v.pause();}),{threshold:0.3});document.querySelectorAll('[data-lazy-video]').forEach(v=>obs.observe(v));
Performance
#237 JS Only

Prefetch on Hover

Prefetches a page's resources when the user hovers a link, speeding up navigation.

Attribute
data-prefetch
JS JavaScript
document.querySelectorAll('[data-prefetch]').forEach(a=>{let done=false;a.addEventListener('mouseenter',()=>{if(done)return;done=true;const link=document.createElement('link');link.rel='prefetch';link.href=a.getAttribute('href')||'';document.head.appendChild(link);});});
Performance
#238 CSS Only

will-change Optimizer

Adds will-change:transform to elements before animation starts for GPU compositing.

Attribute
data-will-change
CSS
[data-will-change]{will-change:transform;transform:translateZ(0);}
Performance
#239 CSS Only

Focus Visible Ring

Shows a clear focus ring only for keyboard navigation, hiding it on mouse click.

Attribute
data-focus-ring
CSS
[data-focus-ring]:focus{outline:none;}[data-focus-ring]:focus-visible{outline:2px solid #6366f1;outline-offset:2px;border-radius:4px;}
Accessibility
#240 CSS Only

Screen Reader Only Text

Hides text visually but keeps it accessible to screen readers.

Attribute
data-sr-only
CSS
[data-sr-only]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0;}
Accessibility
#241 JS Only

ARIA Live Announcer

Announces dynamic changes to screen readers via an ARIA live region.

Attribute
data-announce
JS JavaScript
const region=document.createElement('div');Object.assign(region,{role:'status','aria-live':'polite','aria-atomic':'true'});region.style.cssText='position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)';document.body.appendChild(region);document.querySelectorAll('[data-announce]').forEach(el=>{const obs=new MutationObserver(()=>region.textContent=el.textContent);obs.observe(el,{childList:true,subtree:true,characterData:true});});
Accessibility
#242 CSS Only

Reduced Motion Fallback

Disables all animations when the user has prefers-reduced-motion enabled.

Attribute
data-motion-safe
CSS
@media(prefers-reduced-motion:reduce){[data-motion-safe]{animation:none!important;transition:none!important;}}
Accessibility
#243 CSS Only

Responsive Iframe Embed

Locks an iframe (e.g. YouTube) to 16:9 ratio and scales responsively.

Attribute
data-embed
CSS
[data-embed]{position:relative;padding-top:56.25%;width:100%;}[data-embed] iframe{position:absolute;inset:0;width:100%;height:100%;border:none;}
Media
#244 CSS Only

Image Zoom on Hover

Image smoothly scales up on hover, cropped within its container.

Attribute
data-img-zoom
CSS
[data-img-zoom]{overflow:hidden;}[data-img-zoom] img{transition:transform 0.5s ease;width:100%;}[data-img-zoom]:hover img{transform:scale(1.1);}
Media
#245 CSS + JS

Lightbox on Click

Clicking an image opens it full-screen in a lightbox overlay.

Attribute
data-lightbox
CSS
#lightbox-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.9);display:flex;align-items:center;justify-content:center;z-index:9999;opacity:0;pointer-events:none;transition:opacity 0.3s;}#lightbox-overlay.show{opacity:1;pointer-events:auto;}#lightbox-overlay img{max-width:90vw;max-height:90vh;border-radius:8px;}
JS JavaScript
const ov=document.createElement('div');ov.id='lightbox-overlay';ov.innerHTML='<img id="lb-img"><button style="position:absolute;top:1rem;right:1rem;color:#fff;background:none;border:none;font-size:2rem;cursor:pointer">&times;</button>';document.body.appendChild(ov);ov.querySelector('button')?.addEventListener('click',()=>ov.classList.remove('show'));document.querySelectorAll('[data-lightbox]').forEach(img=>{img.style.cursor='zoom-in';img.addEventListener('click',()=>{(ov.querySelector('#lb-img') as HTMLImageElement).src=img.getAttribute('src')||'';ov.classList.add('show');});});
Media
#246 JS Only

Long Press Action

Fires a custom event after the user holds down a button for 600ms.

Attribute
data-long-press
JS JavaScript
document.querySelectorAll('[data-long-press]').forEach(el=>{let t:any;el.addEventListener('pointerdown',()=>{t=setTimeout(()=>el.dispatchEvent(new CustomEvent('long-press',{bubbles:true})),600);});el.addEventListener('pointerup',()=>clearTimeout(t));el.addEventListener('pointerleave',()=>clearTimeout(t));});
Interaction
#247 CSS + JS

Double Click Edit

Makes a paragraph editable inline on double-click.

Attribute
data-dbl-edit
CSS
[data-dbl-edit]:focus{outline:2px solid #6366f1;border-radius:4px;padding:2px 4px;}
JS JavaScript
document.querySelectorAll('[data-dbl-edit]').forEach(el=>{el.addEventListener('dblclick',()=>{el.contentEditable='true';el.focus();});el.addEventListener('blur',()=>el.contentEditable='false');});
Interaction
#248 JS Only

Swipe Detector

Detects left/right swipe gestures and dispatches custom swipe events.

Attribute
data-swipe
JS JavaScript
document.querySelectorAll('[data-swipe]').forEach(el=>{let sx=0;el.addEventListener('touchstart',e=>sx=e.touches[0].clientX,{passive:true});el.addEventListener('touchend',e=>{const dx=e.changedTouches[0].clientX-sx;if(Math.abs(dx)>40)el.dispatchEvent(new CustomEvent(dx>0?'swipe-right':'swipe-left',{bubbles:true}));});});
Interaction
#249 CSS Only

CSS Variable Theme Switcher

Swaps a complete color theme by toggling a data attribute on <html>.

Attribute
data-theme="dark"
CSS
[data-theme="light"]{--bg:#ffffff;--text:#111827;--accent:#6366f1;}[data-theme="dark"]{--bg:#0a0a0c;--text:#f9fafb;--accent:#818cf8;}
Color & Theme
#250 JS Only

System Color Scheme

Automatically applies dark or light theme based on OS preference.

Attribute
data-auto-theme
JS JavaScript
if(window.matchMedia('(prefers-color-scheme: dark)').matches){document.documentElement.setAttribute('data-theme','dark');}else{document.documentElement.setAttribute('data-theme','light');}window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change',e=>document.documentElement.setAttribute('data-theme',e.matches?'dark':'light'));
Color & Theme
#251 CSS Only

Wave Underline

Animated wavy underline beneath text on hover.

Attribute
data-wave-underline
CSS
[data-wave-underline]{text-decoration:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='4'%3E%3Cpath d='M0 3 Q5 0 10 3 Q15 6 20 3' stroke='%236366f1' fill='none' stroke-width='1.5'/%3E%3C/svg%3E");background-repeat:repeat-x;background-position:0 100%;background-size:20px 4px;padding-bottom:4px;}
Animations
#252 CSS Only

Floating Animation

Element bobs up and down with a gentle floating animation.

Attribute
data-float
CSS
[data-float]{animation:floatBob 3s ease-in-out infinite;}@keyframes floatBob{0%,100%{transform:translateY(0);}50%{transform:translateY(-12px);}}
Animations
#253 CSS Only

Shake on Error

Element shakes horizontally to signal a validation error.

Attribute
data-shake
CSS
[data-shake].error{animation:shake 0.5s cubic-bezier(.36,.07,.19,.97) both;}@keyframes shake{0%,100%{transform:translateX(0);}20%,60%{transform:translateX(-6px);}40%,80%{transform:translateX(6px);}}
Animations
#254 CSS Only

Spin Loader

Circular spinning loader indicator.

Attribute
data-spinner
CSS
[data-spinner]{width:32px;height:32px;border:3px solid rgba(99,102,241,0.2);border-top-color:#6366f1;border-radius:50%;animation:spin 0.7s linear infinite;}@keyframes spin{to{transform:rotate(360deg);}}
Animations
#255 CSS Only

Dot Pulse Loader

Three dots pulse in sequence like a typing indicator.

Attribute
data-dot-pulse
CSS
[data-dot-pulse]{display:flex;gap:6px;}[data-dot-pulse] span{width:8px;height:8px;border-radius:50%;background:#6366f1;animation:dotPulse 1.2s ease-in-out infinite;}[data-dot-pulse] span:nth-child(2){animation-delay:0.2s;}[data-dot-pulse] span:nth-child(3){animation-delay:0.4s;}@keyframes dotPulse{0%,80%,100%{transform:scale(0.7);opacity:0.5;}40%{transform:scale(1);opacity:1;}}
Animations
#256 CSS Only

Elastic Scale on Click

Element springs back after being pressed, giving a satisfying elastic feel.

Attribute
data-elastic
CSS
[data-elastic]{transition:transform 0.1s;}[data-elastic]:active{transform:scale(0.9);}
Animations
#257 CSS Only

Radial Reveal

Content reveals via a circle expand from center using clip-path.

Attribute
data-radial-reveal
CSS
[data-radial-reveal]{clip-path:circle(0% at 50% 50%);animation:radialReveal linear both;animation-timeline:view();animation-range:entry 0% entry 30%;}@keyframes radialReveal{to{clip-path:circle(75% at 50% 50%);}}
Animations
#258 CSS Only

Wipe Right Reveal

Element is revealed with a left-to-right wipe using clip-path.

Attribute
data-wipe-right
CSS
[data-wipe-right]{clip-path:inset(0 100% 0 0);animation:wipeRight linear both;animation-timeline:view();animation-range:entry 0% entry 25%;}@keyframes wipeRight{to{clip-path:inset(0 0% 0 0);}}
Animations
#259 JS Only

Counter Up on Scroll

Number increments from 0 to its value when visible in the viewport.

Attribute
data-counter
JS JavaScript
document.querySelectorAll('[data-counter]').forEach(el=>{const target=parseInt(el.dataset.counter||el.textContent||'0');const obs=new IntersectionObserver(([e])=>{if(!e.isIntersecting)return;obs.disconnect();let n=0;const step=Math.ceil(target/60);const id=setInterval(()=>{n=Math.min(n+step,target);el.textContent=String(n);if(n>=target)clearInterval(id);},16);});obs.observe(el);});
Animations
#260 CSS Only

Color Pop on Scroll

Background color saturates and brightens as the element enters the viewport.

Attribute
data-color-pop
CSS
[data-color-pop]{filter:saturate(0.2) brightness(0.7);animation:colorPop linear both;animation-timeline:view();animation-range:entry 0% entry 35%;}@keyframes colorPop{to{filter:saturate(1) brightness(1);}}
Animations
#261 JS Only

Read Time Estimator

Calculates and displays estimated reading time for an article.

Attribute
data-read-time
JS JavaScript
document.querySelectorAll('[data-read-time]').forEach(el=>{const words=el.textContent?.trim().split(/s+/).length||0;const mins=Math.max(1,Math.round(words/200));const out=document.querySelector('[data-read-time-display]');if(out)out.textContent=`${mins} min read`;});
Scroll
#262 JS Only

Scroll Depth Tracking

Tracks 25/50/75/100% scroll depth and fires custom events for analytics.

Attribute
data-scroll-depth
JS JavaScript
const marks=[25,50,75,100];const fired=new Set<number>();window.addEventListener('scroll',()=>{const pct=Math.round(scrollY/(document.body.scrollHeight-innerHeight)*100);marks.forEach(m=>{if(pct>=m&&!fired.has(m)){fired.add(m);window.dispatchEvent(new CustomEvent('scroll-depth',{detail:m}));}});},{passive:true});
Scroll
#263 CSS Only

Fade Out on Scroll Exit

Element fades out and slides up as it exits the top of the viewport.

Attribute
data-fade-exit
CSS
[data-fade-exit]{animation:fadeExit linear both;animation-timeline:view();animation-range:exit 0% exit 30%;}@keyframes fadeExit{to{opacity:0;transform:translateY(-30px);}}
Scroll
#264 CSS Only

Zoom Out on Scroll Exit

Element shrinks away as it leaves the top of the viewport.

Attribute
data-zoom-exit
CSS
[data-zoom-exit]{animation:zoomExit linear both;animation-timeline:view();animation-range:exit 0% exit 25%;}@keyframes zoomExit{to{opacity:0;transform:scale(0.8);}}
Scroll
#265 CSS Only

Sticky Table Header

Table's thead stays fixed at the top while scrolling through rows.

Attribute
data-sticky-thead
CSS
[data-sticky-thead] thead th{position:sticky;top:0;background:var(--bg,#fff);z-index:1;}
Scroll
#266 CSS + JS

Eye Tracking Eyes

Two eyes in an element that follow the cursor position.

Attribute
data-eyes
CSS
[data-eyes]{display:flex;gap:10px;}.wm-eye{width:24px;height:24px;background:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;}.wm-pupil{width:10px;height:10px;background:#1a1a2e;border-radius:50%;transition:transform 0.1s;}
JS JavaScript
document.querySelectorAll('[data-eyes]').forEach(container=>{const pupils=[...container.querySelectorAll('.wm-pupil')];document.addEventListener('mousemove',e=>{pupils.forEach(pupil=>{const eye=pupil.parentElement!;const r=eye.getBoundingClientRect();const cx=r.left+r.width/2;const cy=r.top+r.height/2;const angle=Math.atan2(e.clientY-cy,e.clientX-cx);const dist=Math.min(5,Math.hypot(e.clientX-cx,e.clientY-cy)*0.3);pupil.style.transform=`translate(${Math.cos(angle)*dist}px,${Math.sin(angle)*dist}px)`;});});});
Cursor
#267 JS Only

Cursor Scale on Clickable

Custom cursor enlarges when hovering over interactive elements.

Attribute
data-cursor-scale
JS JavaScript
const dot=document.querySelector('[data-cursor-dot]') as HTMLElement|null;if(dot){document.querySelectorAll('a,button,[data-cursor-scale]').forEach(el=>{el.addEventListener('mouseenter',()=>dot.style.transform='translate(-50%,-50%) scale(3)');el.addEventListener('mouseleave',()=>dot.style.transform='translate(-50%,-50%) scale(1)');});}
Cursor
#268 CSS + JS

Highlighted Keyword

Wraps matched keywords in a highlight span using JS.

Attribute
data-highlight-words="webflow,css"
CSS
.wm-highlight{background:rgba(99,102,241,0.2);color:#818cf8;border-radius:3px;padding:0 3px;}
JS JavaScript
document.querySelectorAll('[data-highlight-words]').forEach(el=>{const words=(el.dataset.highlightWords||'').split(',');let html=el.innerHTML;words.forEach(w=>{const re=new RegExp(`(\\b${w}\\b)`,'gi');html=html.replace(re,'<mark class="wm-highlight">$1</mark>');});el.innerHTML=html;});
Text
#269 CSS Only

Text Balance Heading

Uses text-wrap:balance for evenly balanced multi-line headings.

Attribute
data-balance
CSS
[data-balance]{text-wrap:balance;}
Text
#270 CSS Only

Clamp Lines

Clamps text to N lines with an ellipsis overflow.

Attribute
data-clamp="3"
CSS
[data-clamp]{display:-webkit-box;-webkit-line-clamp:attr(data-clamp number,3);-webkit-box-orient:vertical;overflow:hidden;}[data-clamp="2"]{-webkit-line-clamp:2;}[data-clamp="3"]{-webkit-line-clamp:3;}[data-clamp="4"]{-webkit-line-clamp:4;}
Text
#271 CSS Only

Redacted Text

Covers text with black bars like a redacted document.

Attribute
data-redact
CSS
[data-redact]{background:#111;color:transparent;border-radius:2px;user-select:none;}
Text
#272 JS Only

Number Format

Auto-formats number inputs with thousands separators on blur.

Attribute
data-num-format
JS JavaScript
document.querySelectorAll('[data-num-format]').forEach(el=>{el.addEventListener('blur',()=>{const n=parseFloat((el as HTMLInputElement).value.replace(/,/g,''));if(!isNaN(n))(el as HTMLInputElement).value=n.toLocaleString();});});
Text
#273 CSS Only

Glassmorphism Panel

Panel with deep glass blur, subtle border, and inner highlight.

Attribute
data-glass-panel
CSS
[data-glass-panel]{background:rgba(255,255,255,0.05);backdrop-filter:blur(20px) saturate(140%);-webkit-backdrop-filter:blur(20px) saturate(140%);border:1px solid rgba(255,255,255,0.1);border-radius:20px;box-shadow:0 8px 32px rgba(0,0,0,0.3),inset 0 1px 0 rgba(255,255,255,0.07);}
Visual FX
#274 CSS Only

Noise Gradient Background

Layered gradient with SVG noise filter for a textured modern look.

Attribute
data-noise-bg
CSS
[data-noise-bg]{position:relative;}[data-noise-bg]::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,#6366f1,#ec4899);border-radius:inherit;}[data-noise-bg]::after{content:'';position:absolute;inset:0;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E");border-radius:inherit;}
Visual FX
#275 CSS Only

Aurora Background

Animated aurora borealis gradient that shifts slowly.

Attribute
data-aurora
CSS
[data-aurora]{background:linear-gradient(125deg,#0ea5e9,#6366f1,#ec4899,#f59e0b);background-size:400% 400%;animation:aurora 8s ease infinite;}@keyframes aurora{0%,100%{background-position:0% 50%;}50%{background-position:100% 50%;}}
Visual FX
#276 CSS Only

Dot Grid Background

Renders a crisp dot-grid pattern background with CSS radial gradient.

Attribute
data-dot-grid
CSS
[data-dot-grid]{background-color:#0a0a0c;background-image:radial-gradient(rgba(255,255,255,0.06) 1px,transparent 1px);background-size:24px 24px;}
Visual FX
#277 CSS Only

Starburst Icon

Animated star burst / sparkle decoration using CSS.

Attribute
data-starburst
CSS
[data-starburst]{display:inline-block;animation:starSpin 4s linear infinite;}@keyframes starSpin{to{transform:rotate(360deg);}}
Visual FX
#278 CSS Only

Caret Blink Text

Shows a blinking text cursor caret at the end of an element.

Attribute
data-caret
CSS
[data-caret]::after{content:'';display:inline-block;width:2px;height:1em;background:currentColor;vertical-align:text-bottom;margin-left:1px;animation:caretBlink 0.9s step-end infinite;}@keyframes caretBlink{50%{opacity:0;}}
Visual FX
#279 CSS Only

Border Draw on Hover

SVG-style border draws itself around a card on hover using clip-path.

Attribute
data-border-draw
CSS
[data-border-draw]{outline:2px solid transparent;outline-offset:2px;transition:outline-color 0s;}[data-border-draw]::before{content:'';position:absolute;inset:0;border:2px solid #6366f1;border-radius:inherit;clip-path:inset(0 100% 0 0);transition:clip-path 0.35s ease;}[data-border-draw]:hover::before{clip-path:inset(0 0% 0 0);}
Visual FX
#280 CSS Only

Color Shift on Scroll

Element's hue-rotate animates as the user scrolls past it.

Attribute
data-hue-scroll
CSS
[data-hue-scroll]{animation:hueScroll linear both;animation-timeline:view();}@keyframes hueScroll{from{filter:hue-rotate(0deg);}to{filter:hue-rotate(360deg);}}
Visual FX
#281 CSS + JS

Notification Toast

Slides in a toast message from the bottom-right corner.

Attribute
data-toast
CSS
[data-toast]{position:fixed;bottom:1.5rem;right:1.5rem;background:#1a1a2e;color:#fff;padding:0.75rem 1.25rem;border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,0.4);transform:translateY(120%);transition:transform 0.35s cubic-bezier(.4,0,.2,1);z-index:9999;}[data-toast].show{transform:none;}
JS JavaScript
document.querySelectorAll('[data-toast-trigger]').forEach(btn=>{const id=btn.dataset.toastTrigger;const toast=document.getElementById(id||'');if(!toast)return;btn.addEventListener('click',()=>{toast.classList.add('show');setTimeout(()=>toast.classList.remove('show'),3000);});});
UI Patterns
#282 CSS Only

Progress Steps

Visual step indicator that highlights the current and completed steps.

Attribute
data-steps
CSS
[data-steps]{display:flex;align-items:center;gap:0;}[data-steps] .step{width:32px;height:32px;border-radius:50%;background:#333;display:flex;align-items:center;justify-content:center;font-size:0.8rem;color:#fff;}[data-steps] .step.done{background:#22c55e;}[data-steps] .step.active{background:#6366f1;}[data-steps] .step-line{flex:1;height:2px;background:#333;}
UI Patterns
#283 CSS + JS

Pricing Toggle (Monthly/Annual)

Switches price display between monthly and annual with a toggle.

Attribute
data-price-toggle
CSS
[data-price-monthly],[data-price-annual]{transition:opacity 0.3s,display 0s;}
JS JavaScript
const tog=document.querySelector('[data-price-toggle]');tog?.addEventListener('change',()=>{const isAnnual=(tog as HTMLInputElement).checked;document.querySelectorAll('[data-price-monthly]').forEach(el=>(el as HTMLElement).style.display=isAnnual?'none':'');document.querySelectorAll('[data-price-annual]').forEach(el=>(el as HTMLElement).style.display=isAnnual?'':'none');});
UI Patterns
#284 CSS + JS

Collapsible Section

Toggles a content section open/closed with smooth height animation.

Attribute
data-collapse
CSS
[data-collapse-body]{display:grid;grid-template-rows:0fr;transition:grid-template-rows 0.35s ease;}[data-collapse-body].open{grid-template-rows:1fr;}[data-collapse-body]>*{overflow:hidden;}
JS JavaScript
document.querySelectorAll('[data-collapse-trigger]').forEach(btn=>{const target=document.getElementById(btn.dataset.collapseTrigger||'');btn.addEventListener('click',()=>target?.classList.toggle('open'));});
UI Patterns
#285 CSS + JS

Pagination Component

Generates numbered pagination links for a list dynamically.

Attribute
data-paginate
CSS
[data-page-btn]{padding:0.3rem 0.7rem;border:1px solid rgba(255,255,255,0.1);border-radius:6px;cursor:pointer;}[data-page-btn].active{background:#6366f1;border-color:#6366f1;color:#fff;}
JS JavaScript
const list=document.querySelector('[data-paginate]');const btns=document.querySelectorAll('[data-page-btn]');btns.forEach((btn,i)=>{btn.addEventListener('click',()=>{btns.forEach(b=>b.classList.remove('active'));btn.classList.add('active');list?.dispatchEvent(new CustomEvent('page-change',{detail:i+1}));});});
UI Patterns
#286 JS Only

Scroll-into-Focus Input

Input scrolls smoothly into view and focuses when a label is clicked.

Attribute
data-focus-scroll
JS JavaScript
document.querySelectorAll('[data-focus-scroll]').forEach(label=>{label.addEventListener('click',()=>{const id=label.getAttribute('for');const input=document.getElementById(id||'');input?.scrollIntoView({behavior:'smooth',block:'center'});input?.focus();});});
UI Patterns
#287 CSS Only

Container Query Card

Card changes layout based on its own container width, not the viewport.

Attribute
data-cq-card
CSS
[data-cq-card]{container-type:inline-size;}@container(min-width:400px){[data-cq-card] .inner{display:flex;gap:1rem;}}
Layout
#288 CSS Only

Holy Grail Layout

Classic holy grail: header, footer, left nav, main, right sidebar.

Attribute
data-holy-grail
CSS
[data-holy-grail]{display:grid;grid-template:'head head head' auto 'nav main aside' 1fr 'foot foot foot' auto/200px 1fr 200px;min-height:100vh;gap:0;}
Layout
#289 CSS Only

Text Column Balance

Balances text across multiple CSS columns automatically.

Attribute
data-col-text
CSS
[data-col-text]{columns:2 320px;column-gap:2rem;column-rule:1px solid rgba(255,255,255,0.1);}
Layout
#290 CSS Only

Intrinsic Ratio Wrapper

Wraps any embed in a container that maintains its aspect ratio.

Attribute
data-ratio="16/9"
CSS
[data-ratio]{position:relative;}[data-ratio="16/9"]{padding-top:56.25%;}[data-ratio="4/3"]{padding-top:75%;}[data-ratio] iframe,[data-ratio] video{position:absolute;inset:0;width:100%;height:100%;}
Layout
#291 CSS + JS

Range Slider with Value Display

Shows the current value of a range input as you drag the thumb.

Attribute
data-range-display
CSS
[data-range-output]{font-size:0.85rem;font-weight:600;color:#6366f1;}
JS JavaScript
document.querySelectorAll('[data-range-display]').forEach(input=>{const out=document.getElementById(input.dataset.rangeDisplay||'');if(out)out.textContent=(input as HTMLInputElement).value;input.addEventListener('input',()=>{if(out)out.textContent=(input as HTMLInputElement).value;});});
Forms
#292 CSS + JS

Multi-step Form

Hides all form steps except the current one, with Next/Back controls.

Attribute
data-multistep
CSS
[data-step]{display:none;}[data-step].active{display:block;}
JS JavaScript
let cur=0;const steps=[...document.querySelectorAll('[data-step]')];const show=(n:number)=>{steps.forEach((s,i)=>s.classList.toggle('active',i===n));};show(0);document.querySelectorAll('[data-next-step]').forEach(btn=>btn.addEventListener('click',()=>show(Math.min(++cur,steps.length-1))));document.querySelectorAll('[data-prev-step]').forEach(btn=>btn.addEventListener('click',()=>show(Math.max(--cur,0))));
Forms
#293 CSS + JS

Real-time Email Validation

Shows a tick/cross icon as the user types to indicate valid email format.

Attribute
data-email-validate
CSS
[data-email-validate].valid{border-color:#22c55e;}[data-email-validate].invalid{border-color:#ef4444;}
JS JavaScript
document.querySelectorAll('[data-email-validate]').forEach(el=>{el.addEventListener('input',()=>{const ok=/^[^s@]+@[^s@]+.[^s@]+$/.test((el as HTMLInputElement).value);el.classList.toggle('valid',ok);el.classList.toggle('invalid',(el as HTMLInputElement).value.length>0&&!ok);});});
Forms
#294 JS Only

Credit Card Formatter

Auto-formats credit card number input with spaces every 4 digits.

Attribute
data-cc-format
JS JavaScript
document.querySelectorAll('[data-cc-format]').forEach(el=>{el.addEventListener('input',()=>{let v=(el as HTMLInputElement).value.replace(/D/g,'').slice(0,16);(el as HTMLInputElement).value=v.replace(/(.{4})/g,'$1 ').trim();});});
Forms
#295 CSS + JS

One Page Scroll Indicator

Dots on the side indicate which section is currently in view.

Attribute
data-onepage
CSS
[data-onepage-dots]{position:fixed;right:1rem;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:8px;z-index:100;}[data-onepage-dot]{width:8px;height:8px;border-radius:50%;background:rgba(255,255,255,0.2);cursor:pointer;transition:background 0.2s,transform 0.2s;}[data-onepage-dot].active{background:#6366f1;transform:scale(1.4);}
JS JavaScript
const sections=document.querySelectorAll('[data-onepage]');const dots=[...sections].map(()=>{const d=document.createElement('div');d.setAttribute('data-onepage-dot','');return d;});const wrap=document.createElement('div');wrap.setAttribute('data-onepage-dots','');dots.forEach((d,i)=>{d.addEventListener('click',()=>sections[i].scrollIntoView({behavior:'smooth'}));wrap.appendChild(d);});document.body.appendChild(wrap);const obs=new IntersectionObserver(es=>es.forEach(e=>{const i=[...sections].indexOf(e.target as HTMLElement);if(i>-1)dots[i].classList.toggle('active',e.isIntersecting);}),{threshold:0.5});sections.forEach(s=>obs.observe(s));
Navigation
#296 CSS + JS

Hamburger to X Animation

Animated hamburger icon that morphs into an X when toggled.

Attribute
data-hamburger
CSS
[data-hamburger]{display:flex;flex-direction:column;gap:5px;cursor:pointer;width:24px;}[data-hamburger] span{height:2px;background:currentColor;border-radius:1px;transition:transform 0.3s,opacity 0.3s;}[data-hamburger].open span:nth-child(1){transform:translateY(7px) rotate(45deg);}[data-hamburger].open span:nth-child(2){opacity:0;}[data-hamburger].open span:nth-child(3){transform:translateY(-7px) rotate(-45deg);}
JS JavaScript
document.querySelectorAll('[data-hamburger]').forEach(el=>el.addEventListener('click',()=>el.classList.toggle('open')));
Navigation
#297 JS Only

Prevent Double Submit

Disables a form's submit button after first submission to prevent duplicates.

Attribute
data-once-submit
JS JavaScript
document.querySelectorAll('[data-once-submit]').forEach(form=>{form.addEventListener('submit',()=>{form.querySelectorAll('[type=submit]').forEach((btn:Element)=>{(btn as HTMLButtonElement).disabled=true;(btn as HTMLButtonElement).textContent='Sending…';});});});
Utility
#298 CSS + JS

Idle Timeout Warning

Shows a modal after 2 minutes of user inactivity.

Attribute
data-idle-timeout
CSS
[data-idle-modal]{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.7);align-items:center;justify-content:center;z-index:9999;}[data-idle-modal].show{display:flex;}
JS JavaScript
let idleTimer:any;const reset=()=>{clearTimeout(idleTimer);idleTimer=setTimeout(()=>document.querySelector('[data-idle-modal]')?.classList.add('show'),120000);};['click','keydown','mousemove','scroll'].forEach(ev=>document.addEventListener(ev,reset));reset();
Utility
#299 JS Only

Keyboard Shortcut Handler

Listens for a keyboard shortcut and triggers an action.

Attribute
data-shortcut="ctrl+k"
JS JavaScript
document.querySelectorAll('[data-shortcut]').forEach(el=>{const [mod,key]=(el.dataset.shortcut||'').toLowerCase().split('+');document.addEventListener('keydown',e=>{const ok=(mod==='ctrl'&&e.ctrlKey)||(mod==='meta'&&e.metaKey)||(mod==='shift'&&e.shiftKey);if(ok&&e.key.toLowerCase()===key){e.preventDefault();el.dispatchEvent(new Event('shortcut'));}});});
Utility
#300 JS Only

Smooth Anchor Offset

Scroll-to-anchor offsets for fixed headers so content isn't hidden.

Attribute
data-offset-anchor
JS JavaScript
document.querySelectorAll('a[href^="#"]').forEach(a=>{a.addEventListener('click',e=>{const id=a.getAttribute('href');const target=document.querySelector(id||'');if(target){e.preventDefault();const offset=parseInt(document.documentElement.dataset.offsetAnchor||'80');window.scrollTo({top:target.getBoundingClientRect().top+scrollY-offset,behavior:'smooth'});}});});
Utility
#301 JS Only

Resource Hints Injector

Automatically injects dns-prefetch and preconnect hints for external domains.

Attribute
data-resource-hints
JS JavaScript
document.querySelectorAll('[data-resource-hints]').forEach(el=>{(el.dataset.resourceHints||'').split(',').forEach(domain=>{['dns-prefetch','preconnect'].forEach(rel=>{const link=document.createElement('link');link.rel=rel;link.href=domain.trim();document.head.appendChild(link);});});});
Performance
#302 JS Only

Throttle Scroll Handler

Wraps any scroll callback in a throttle to limit firing rate.

Attribute
data-throttle-scroll
JS JavaScript
let ticking=false;window.addEventListener('scroll',()=>{if(!ticking){requestAnimationFrame(()=>{document.querySelector('[data-throttle-scroll]')?.dispatchEvent(new Event('throttled-scroll'));ticking=false;});ticking=true;}},{passive:true});
Performance
#303 CSS + JS

High Contrast Mode

Adds a toggle for high-contrast mode that respects WCAG AA contrast ratios.

Attribute
data-high-contrast
CSS
[data-high-contrast] *{background:#000!important;color:#fff!important;border-color:#fff!important;}
JS JavaScript
document.querySelector('[data-hc-toggle]')?.addEventListener('click',()=>document.documentElement.toggleAttribute('data-high-contrast'));
Accessibility
#304 JS Only

Font Size Adjuster

Buttons to increase or decrease the root font size for readability.

Attribute
data-font-size
JS JavaScript
let base=parseFloat(getComputedStyle(document.documentElement).fontSize);document.querySelectorAll('[data-font-size]').forEach(btn=>{btn.addEventListener('click',()=>{base=Math.min(Math.max(base+(btn.dataset.fontSize==='+'?2:-2),12),24);document.documentElement.style.fontSize=base+'px';});});
Accessibility
#305 JS Only

Tab Trap in Modal

Traps keyboard focus inside an open modal so tab doesn't escape.

Attribute
data-trap-focus
JS JavaScript
document.querySelectorAll('[data-trap-focus]').forEach(modal=>{modal.addEventListener('keydown',e=>{if(e.key!=='Tab')return;const focusable=[...modal.querySelectorAll<HTMLElement>('a,button,input,select,textarea,[tabindex]:not([tabindex="-1"])')];const first=focusable[0];const last=focusable[focusable.length-1];if(e.shiftKey){if(document.activeElement===first){e.preventDefault();last.focus();}}else{if(document.activeElement===last){e.preventDefault();first.focus();}}});});
Accessibility
#306 CSS + JS

Video Progress Bar

Shows a custom progress bar below a video that updates as it plays.

Attribute
data-video-progress
CSS
[data-video-track]{height:3px;background:rgba(255,255,255,0.15);margin-top:4px;}[data-video-fill]{height:100%;background:#6366f1;width:0%;transition:width 0.2s;}
JS JavaScript
document.querySelectorAll('[data-video-progress]').forEach(video=>{const fill=video.parentElement?.querySelector('[data-video-fill]');(video as HTMLVideoElement).addEventListener('timeupdate',()=>{if(fill)(fill as HTMLElement).style.width=((video as HTMLVideoElement).currentTime/(video as HTMLVideoElement).duration*100)+'%';});});
Media
#307 CSS + JS

Image Compare Slider

Drag a divider to compare two images side by side.

Attribute
data-img-compare
CSS
[data-img-compare]{position:relative;overflow:hidden;cursor:col-resize;}[data-img-compare] .img-before{position:absolute;inset:0;clip-path:inset(0 50% 0 0);}[data-img-compare] .img-handle{position:absolute;top:0;left:50%;transform:translateX(-50%);width:3px;height:100%;background:#fff;cursor:col-resize;}
JS JavaScript
document.querySelectorAll('[data-img-compare]').forEach(el=>{const before=el.querySelector<HTMLElement>('.img-before');const handle=el.querySelector<HTMLElement>('.img-handle');let active=false;el.addEventListener('mousedown',()=>active=true);document.addEventListener('mouseup',()=>active=false);el.addEventListener('mousemove',e=>{if(!active)return;const r=el.getBoundingClientRect();const pct=((e.clientX-r.left)/r.width)*100;if(before)before.style.clipPath=`inset(0 ${100-pct}% 0 0)`;if(handle)handle.style.left=pct+'%';});});
Media
#308 CSS + JS

Drag to Reorder List

List items can be dragged to reorder using the HTML5 drag API.

Attribute
data-reorder
CSS
[data-reorder] li{cursor:grab;}[data-reorder] li.over{border-top:2px solid #6366f1;}
JS JavaScript
document.querySelectorAll('[data-reorder]').forEach(list=>{let src:HTMLElement|null=null;list.querySelectorAll<HTMLElement>('li').forEach(item=>{item.draggable=true;item.addEventListener('dragstart',()=>src=item);item.addEventListener('dragover',e=>{e.preventDefault();item.classList.add('over');});item.addEventListener('dragleave',()=>item.classList.remove('over'));item.addEventListener('drop',()=>{item.classList.remove('over');if(src&&src!==item)list.insertBefore(src,item);});});});
Interaction
#309 CSS + JS

Pinch to Zoom Image

Enables pinch-to-zoom on a touch device for an image element.

Attribute
data-pinch-zoom
CSS
[data-pinch-zoom]{touch-action:none;transform-origin:center center;}
JS JavaScript
document.querySelectorAll('[data-pinch-zoom]').forEach(el=>{let dist=0;el.addEventListener('touchstart',e=>{if(e.touches.length===2)dist=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);},{passive:true});el.addEventListener('touchmove',e=>{if(e.touches.length===2){const nd=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);(el as HTMLElement).style.transform=`scale(${Math.min(Math.max(nd/dist,1),3)})`;dist=nd;}},{passive:true});});
Interaction
#310 CSS + JS

Context Menu Override

Shows a custom right-click context menu instead of the browser default.

Attribute
data-context-menu
CSS
[data-ctx-menu]{position:fixed;background:#1a1a2e;border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:0.5rem;z-index:9999;display:none;min-width:150px;}[data-ctx-menu].show{display:block;}
JS JavaScript
document.querySelectorAll('[data-context-menu]').forEach(el=>{const menuId=el.dataset.contextMenu;const menu=document.getElementById(menuId||'');if(!menu)return;el.addEventListener('contextmenu',e=>{e.preventDefault();menu.style.left=e.clientX+'px';menu.style.top=e.clientY+'px';menu.classList.add('show');});document.addEventListener('click',()=>menu.classList.remove('show'));});
Interaction
#311 JS Only

Accent Color Picker

Updates CSS --accent variable site-wide from a color input.

Attribute
data-accent-picker
JS JavaScript
document.querySelectorAll('[data-accent-picker]').forEach(input=>{input.addEventListener('input',()=>document.documentElement.style.setProperty('--accent',(input as HTMLInputElement).value));});
Color & Theme
#312 JS Only

Randomize Palette

Generates and applies a random complementary color palette on click.

Attribute
data-random-palette
JS JavaScript
document.querySelectorAll('[data-random-palette]').forEach(btn=>{btn.addEventListener('click',()=>{const h=Math.floor(Math.random()*360);document.documentElement.style.setProperty('--accent',`hsl(${h},80%,60%)`);document.documentElement.style.setProperty('--accent2',`hsl(${(h+120)%360},80%,60%)`);document.documentElement.style.setProperty('--accent3',`hsl(${(h+240)%360},80%,60%)`);});});
Color & Theme
#313 CSS + JS

Gradient on Scroll Direction

Changes background gradient direction based on scroll direction.

Attribute
data-dir-gradient
CSS
[data-dir-gradient]{transition:background 0.5s;}[data-dir-gradient].down{background:linear-gradient(180deg,#6366f1,#ec4899);}[data-dir-gradient].up{background:linear-gradient(0deg,#6366f1,#ec4899);}
JS JavaScript
let lastY=0;window.addEventListener('scroll',()=>{const dir=scrollY>lastY?'down':'up';lastY=scrollY;document.querySelectorAll('[data-dir-gradient]').forEach(el=>{el.classList.toggle('down',dir==='down');el.classList.toggle('up',dir==='up');});},{passive:true});
Color & Theme
#314 JS Only

Easter Egg Konami Code

Triggers a hidden animation when the Konami code is entered on keyboard.

Attribute
data-konami
JS JavaScript
const seq=['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a'];let idx=0;document.addEventListener('keydown',e=>{if(e.key===seq[idx])idx++;else idx=0;if(idx===seq.length){idx=0;document.querySelector('[data-konami]')?.dispatchEvent(new CustomEvent('konami'));}});
Misc
#315 JS Only

Random Placeholder Generator

Cycles through random placeholder texts on each page load.

Attribute
data-random-placeholder
JS JavaScript
document.querySelectorAll('[data-random-placeholder]').forEach(el=>{const opts=(el.dataset.randomPlaceholder||'Type here…,Search…,Enter text…').split(',');(el as HTMLInputElement).placeholder=opts[Math.floor(Math.random()*opts.length)];});
Misc
#316 JS Only

Emoji Rain

Rains random emojis down the screen on click.

Attribute
data-emoji-rain
JS JavaScript
document.querySelectorAll('[data-emoji-rain]').forEach(el=>{el.addEventListener('click',()=>{const emojis=['🎉','✨','🚀','💜','🌟','🎊','💫','🔮'];for(let i=0;i<30;i++){const s=document.createElement('span');s.textContent=emojis[Math.floor(Math.random()*emojis.length)];Object.assign(s.style,{position:'fixed',top:'-2rem',left:Math.random()*100+'vw',fontSize:Math.random()*20+16+'px',pointerEvents:'none',zIndex:'9999',transition:`transform ${Math.random()*2+2}s ease,opacity ${Math.random()*2+2}s ease`,opacity:'1'});document.body.appendChild(s);setTimeout(()=>{s.style.transform=`translateY(${Math.random()*80+60}vh) rotate(${Math.random()*360}deg)`;s.style.opacity='0';},10);setTimeout(()=>s.remove(),4000);}});});
Misc
#317 CSS + JS

Sticky Note Widget

A draggable sticky note users can write on, saved to localStorage.

Attribute
data-sticky-note
CSS
[data-sticky-note]{position:fixed;width:220px;background:#fef08a;color:#1a1a2e;border-radius:4px;padding:0.75rem;box-shadow:4px 4px 10px rgba(0,0,0,0.25);cursor:move;z-index:9000;resize:both;overflow:auto;font-family:monospace;font-size:0.82rem;min-height:120px;}
JS JavaScript
document.querySelectorAll('[data-sticky-note]').forEach(el=>{let ox=0,oy=0,down=false;el.style.top=(localStorage.getItem('sn-top')||'100')+'px';el.style.left=(localStorage.getItem('sn-left')||'100')+'px';(el as HTMLElement).contentEditable='true';el.textContent=localStorage.getItem('sn-text')||'Your note…';el.addEventListener('input',()=>localStorage.setItem('sn-text',el.textContent||''));el.addEventListener('mousedown',e=>{down=true;ox=e.clientX-el.getBoundingClientRect().left;oy=e.clientY-el.getBoundingClientRect().top;});document.addEventListener('mousemove',e=>{if(!down)return;(el as HTMLElement).style.left=e.clientX-ox+'px';(el as HTMLElement).style.top=e.clientY-oy+'px';});document.addEventListener('mouseup',()=>{down=false;localStorage.setItem('sn-left',(el as HTMLElement).style.left);localStorage.setItem('sn-top',(el as HTMLElement).style.top);});});
Misc
#318 JS Only

QR Code from URL

Generates a QR code SVG for the current page URL using a public API.

Attribute
data-qr
CSS
[data-qr] img{width:120px;height:120px;border-radius:8px;}
JS JavaScript
document.querySelectorAll('[data-qr]').forEach(el=>{const img=document.createElement('img');img.src=`https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=${encodeURIComponent(location.href)}`;img.alt='QR Code';el.appendChild(img);});
Misc
#319 CSS + JS

Word Count Live

Displays a real-time word count beneath a textarea.

Attribute
data-word-count
CSS
[data-word-count-display]{font-size:0.75rem;color:#888;}
JS JavaScript
document.querySelectorAll('[data-word-count]').forEach(el=>{const out=document.createElement('span');out.setAttribute('data-word-count-display','');el.after(out);const upd=()=>out.textContent=(el as HTMLTextAreaElement).value.trim().split(/s+/).filter(Boolean).length+' words';upd();el.addEventListener('input',upd);});
Misc
#320 JS Only

Browser Tab Title Changer

Cycles through messages in the browser tab title to re-engage users.

Attribute
data-tab-title
JS JavaScript
const msgs=(document.querySelector('[data-tab-title]') as HTMLElement|null)?.dataset.tabTitle?.split(',')||[];const orig=document.title;let i=0;setInterval(()=>{document.title=msgs[i%msgs.length]||orig;i++;},2500);document.addEventListener('visibilitychange',()=>{if(!document.hidden)document.title=orig;});
Misc
#321 CSS Only

Orbit Animation

Child element orbits around the parent in a continuous circle.

Attribute
data-orbit
CSS
[data-orbit]{position:relative;}[data-orbit] [data-orbit-child]{position:absolute;top:0;left:50%;transform-origin:0 50px;animation:orbitSpin 3s linear infinite;}@keyframes orbitSpin{to{transform:rotate(360deg);}}
Animations
#322 CSS Only

Pendulum Swing

Element swings like a pendulum from its top-center origin.

Attribute
data-pendulum
CSS
[data-pendulum]{transform-origin:top center;animation:swing 1.8s ease-in-out infinite alternate;}@keyframes swing{from{transform:rotate(-20deg);}to{transform:rotate(20deg);}}
Animations
#323 CSS Only

Wobble on Hover

Element wiggles with a satisfying wobble when hovered.

Attribute
data-wobble
CSS
[data-wobble]:hover{animation:wobble 0.6s ease;}@keyframes wobble{0%,100%{transform:rotate(0);}15%{transform:rotate(-6deg);}30%{transform:rotate(5deg);}45%{transform:rotate(-4deg);}60%{transform:rotate(3deg);}75%{transform:rotate(-2deg);}}
Animations
#324 CSS Only

Pop on Hover

Element pops up with a scale spring on hover.

Attribute
data-pop
CSS
[data-pop]{transition:transform 0.2s cubic-bezier(.34,1.56,.64,1);}[data-pop]:hover{transform:scale(1.08);}
Animations
#325 CSS Only

Ink Blot Reveal

Content reveals using an expanding ink-blot circle clip.

Attribute
data-ink-reveal
CSS
[data-ink-reveal]{clip-path:circle(0 at center);animation:inkBlot linear both;animation-timeline:view();animation-range:entry 0% entry 35%;}@keyframes inkBlot{to{clip-path:circle(100% at center);}}
Animations
#326 CSS Only

Scroll-Linked Width Bar

A div's width expands from 0 to 100% relative to scroll position.

Attribute
data-scroll-width
CSS
[data-scroll-width]{animation:scrollWidth linear both;animation-timeline:scroll(root);}@keyframes scrollWidth{from{width:0%;}to{width:100%;}}
Scroll
#327 CSS + JS

Section Active Class

Adds .active to a section when it's the primary visible section.

Attribute
data-section-active
CSS
[data-section-active].active{outline:2px solid #6366f1;}
JS JavaScript
const obs=new IntersectionObserver(es=>{es.forEach(e=>e.target.classList.toggle('active',e.isIntersecting));},{threshold:0.5});document.querySelectorAll('[data-section-active]').forEach(s=>obs.observe(s));
Scroll
#328 CSS + JS

Float Up Cards on Scroll

Cards float up with staggered delays as the section enters the viewport.

Attribute
data-float-cards
CSS
[data-float-cards]>*{opacity:0;transform:translateY(30px);transition:opacity 0.5s,transform 0.5s;}[data-float-cards].in-view>*{opacity:1;transform:none;}
JS JavaScript
const obs=new IntersectionObserver(([e])=>{if(e.isIntersecting){e.target.classList.add('in-view');obs.disconnect();}});document.querySelectorAll('[data-float-cards]').forEach(el=>{[...el.children].forEach((c,i)=>(c as HTMLElement).style.transitionDelay=i*0.08+'s');obs.observe(el);});
Scroll
#329 CSS + JS

Spotlight Card

Reveals a radial light effect at the mouse position inside a card.

Attribute
data-spotlight-card
CSS
[data-spotlight-card]{background:radial-gradient(circle 200px at var(--x,-200px) var(--y,-200px),rgba(99,102,241,0.15),transparent 70%);transition:background 0.05s;}
JS JavaScript
document.querySelectorAll('[data-spotlight-card]').forEach(el=>{el.addEventListener('mousemove',e=>{const r=el.getBoundingClientRect();el.style.setProperty('--x',e.clientX-r.left+'px');el.style.setProperty('--y',e.clientY-r.top+'px');});el.addEventListener('mouseleave',()=>{el.style.setProperty('--x','-200px');el.style.setProperty('--y','-200px');});});
UI Patterns
#330 CSS + JS

Full Screen Section

Expands a section to fill the entire screen on button click.

Attribute
data-fullscreen
CSS
[data-fullscreen].is-full{position:fixed;inset:0;z-index:9000;background:var(--bg,#fff);overflow-y:auto;}
JS JavaScript
document.querySelectorAll('[data-fullscreen-trigger]').forEach(btn=>{const target=document.querySelector('[data-fullscreen]');btn.addEventListener('click',()=>target?.classList.toggle('is-full'));});
UI Patterns
#331 CSS Only

CSS Subgrid

Child elements inherit parent grid tracks for pixel-perfect alignment.

Attribute
data-subgrid
CSS
[data-subgrid]{display:grid;grid-template-columns:subgrid;}
Layout
#332 CSS Only

Responsive Typography Scale

Uses a CSS clamp-based modular type scale for all headings.

Attribute
data-type-scale
CSS
[data-type-scale] h1{font-size:clamp(2rem,5vw+1rem,5rem);}[data-type-scale] h2{font-size:clamp(1.5rem,3vw+0.5rem,3.5rem);}[data-type-scale] h3{font-size:clamp(1.2rem,2vw+0.4rem,2.5rem);}
Layout
#333 CSS + JS

Sidebar Toggle Layout

Collapses sidebar to icon-only mode with a smooth width transition.

Attribute
data-sidebar-toggle
CSS
[data-sidebar]{width:240px;overflow:hidden;transition:width 0.3s ease;}[data-sidebar].collapsed{width:56px;}
JS JavaScript
document.querySelectorAll('[data-sidebar-toggle]').forEach(btn=>{const sb=document.querySelector('[data-sidebar]');btn.addEventListener('click',()=>sb?.classList.toggle('collapsed'));});
Layout
#334 CSS + JS

Tag Input

Lets users type and add comma-separated tags to a list.

Attribute
data-tag-input
CSS
[data-tag-list]{display:flex;flex-wrap:wrap;gap:6px;}.wm-tag{background:#6366f1;color:#fff;border-radius:20px;padding:2px 10px;font-size:0.78rem;display:flex;align-items:center;gap:4px;}
JS JavaScript
document.querySelectorAll('[data-tag-input]').forEach(input=>{const list=input.parentElement?.querySelector('[data-tag-list]');if(!list)return;input.addEventListener('keydown',e=>{if((e.key===','||e.key==='Enter')&&(input as HTMLInputElement).value.trim()){e.preventDefault();const val=(input as HTMLInputElement).value.trim().replace(',','');const tag=document.createElement('span');tag.className='wm-tag';tag.innerHTML=val+'<button onclick="this.parentElement.remove()">×</button>';list.appendChild(tag);(input as HTMLInputElement).value='';}});});
Forms
#335 JS Only

Image AVIF/WebP Picker

Automatically selects the best image format the browser supports.

Attribute
data-best-img
JS JavaScript
document.querySelectorAll('[data-best-img]').forEach(img=>{const avif=img.dataset.bestImgAvif;const webp=img.dataset.bestImgWebp;const fallback=img.dataset.bestImgFallback;const test=(src:string)=>new Promise<boolean>(r=>{const i=new Image();i.onload=()=>r(i.width>0);i.onerror=()=>r(false);i.src=src;});(async()=>{if(avif&&await test(avif))(img as HTMLImageElement).src=avif;else if(webp&&await test(webp))(img as HTMLImageElement).src=webp;else if(fallback)(img as HTMLImageElement).src=fallback;})();});
Performance
#336 JS Only

Announce Page Title on Route Change

Re-announces the document title to screen readers on SPA navigation.

Attribute
data-announce-title
JS JavaScript
const live=document.createElement('div');Object.assign(live.style,{position:'absolute',width:'1px',height:'1px',overflow:'hidden',clip:'rect(0,0,0,0)'});live.setAttribute('aria-live','polite');live.setAttribute('aria-atomic','true');document.body.appendChild(live);new MutationObserver(()=>{live.textContent='';setTimeout(()=>live.textContent=document.title,100);}).observe(document.querySelector('title')!,{childList:true});
Accessibility
#337 JS Only

Audio Visualizer Bar

Renders a pulsing bar visualizer for a playing audio element.

Attribute
data-audio-vis
CSS
[data-audio-bars]{display:flex;gap:3px;align-items:flex-end;height:40px;}[data-audio-bar]{width:4px;border-radius:2px;background:#6366f1;transition:height 0.1s;}
JS JavaScript
document.querySelectorAll('[data-audio-vis]').forEach(audio=>{const bars=audio.parentElement?.querySelectorAll('[data-audio-bar]');const ctx=new AudioContext();const src=ctx.createMediaElementSource(audio as HTMLMediaElement);const ana=ctx.createAnalyser();src.connect(ana);ana.connect(ctx.destination);const data=new Uint8Array(ana.frequencyBinCount);const draw=()=>{requestAnimationFrame(draw);ana.getByteFrequencyData(data);bars?.forEach((b,i)=>{(b as HTMLElement).style.height=data[i*4]+'px';});};draw();});
Media
#338 CSS + JS

Hold to Delete

User must hold a button for 2 seconds to confirm deletion — prevents accidents.

Attribute
data-hold-delete
CSS
[data-hold-delete]{position:relative;overflow:hidden;}[data-hold-delete]::after{content:'';position:absolute;inset:0;background:#ef4444;transform:scaleX(0);transform-origin:left;transition:transform var(--hold-dur,2s) linear;}[data-hold-delete].holding::after{transform:scaleX(1);}
JS JavaScript
document.querySelectorAll('[data-hold-delete]').forEach(el=>{let timer:any;el.addEventListener('mousedown',()=>{el.classList.add('holding');timer=setTimeout(()=>el.dispatchEvent(new CustomEvent('confirmed-delete')),2000);});['mouseup','mouseleave'].forEach(ev=>el.addEventListener(ev,()=>{clearTimeout(timer);el.classList.remove('holding');}));});
Interaction
#339 JS Only

Scroll to Reveal Password

Reveals a hidden password field when a specific element scrolls into view.

Attribute
data-scroll-reveal-pw
JS JavaScript
const obs=new IntersectionObserver(([e])=>{if(e.isIntersecting)document.querySelectorAll('[data-scroll-reveal-pw]').forEach(el=>(el as HTMLInputElement).type='text');});const target=document.querySelector('[data-pw-reveal-trigger]');if(target)obs.observe(target);
Interaction
#340 CSS Only

Brand Color Variables

Defines a set of brand color CSS variables on the root element.

Attribute
data-brand-colors
CSS
:root{--brand-primary:#6366f1;--brand-secondary:#ec4899;--brand-success:#22c55e;--brand-warning:#f59e0b;--brand-error:#ef4444;--brand-text:#f9fafb;--brand-bg:#0a0a0c;}
Color & Theme
#341 CSS Only

Tinted Image Overlay

Adds a colored tint overlay to an image using CSS mix-blend-mode.

Attribute
data-tint
CSS
[data-tint]{position:relative;}[data-tint]::after{content:'';position:absolute;inset:0;background:rgba(99,102,241,0.4);mix-blend-mode:multiply;border-radius:inherit;}
Color & Theme
#342 CSS + JS

Sepia Filter Toggle

Toggles a sepia-tone CSS filter on the entire page.

Attribute
data-sepia
CSS
html.sepia *{filter:sepia(0.9);}
JS JavaScript
document.querySelector('[data-sepia]')?.addEventListener('click',()=>document.documentElement.classList.toggle('sepia'));
Color & Theme
#343 JS Only

Copy Page URL Button

Copies the current page URL to clipboard on click.

Attribute
data-copy-url
JS JavaScript
document.querySelectorAll('[data-copy-url]').forEach(btn=>{btn.addEventListener('click',()=>{navigator.clipboard.writeText(location.href).then(()=>{const prev=btn.textContent;btn.textContent='Copied!';setTimeout(()=>btn.textContent=prev,2000);});});});
Misc
#344 JS Only

Social Share Button

Opens native share dialog or falls back to Twitter share link.

Attribute
data-share
JS JavaScript
document.querySelectorAll('[data-share]').forEach(btn=>{btn.addEventListener('click',()=>{if(navigator.share)navigator.share({title:document.title,url:location.href});else window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(location.href)}`,'_blank');});});
Misc
#345 CSS + JS

Online Status Badge

Shows a green/red badge reflecting whether the user is online or offline.

Attribute
data-online-badge
CSS
[data-online-badge]::after{content:'Online';background:#22c55e;color:#fff;padding:2px 8px;border-radius:20px;font-size:0.7rem;}[data-online-badge].offline::after{content:'Offline';background:#ef4444;}
JS JavaScript
const el=document.querySelector('[data-online-badge]');const upd=()=>el?.classList.toggle('offline',!navigator.onLine);upd();window.addEventListener('online',upd);window.addEventListener('offline',upd);
Misc
#346 CSS + JS

Command Palette

Opens a searchable command palette overlay with Cmd+K.

Attribute
data-cmd-palette
CSS
[data-cmd-palette]{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:9999;align-items:flex-start;justify-content:center;padding-top:15vh;}[data-cmd-palette].open{display:flex;}[data-cmd-palette] .palette-box{background:#1a1a2e;border:1px solid rgba(255,255,255,0.1);border-radius:12px;width:min(560px,90vw);padding:0.75rem;}[data-cmd-palette] input{width:100%;background:none;border:none;color:#fff;font-size:1.1rem;outline:none;}
JS JavaScript
const pal=document.querySelector('[data-cmd-palette]');document.addEventListener('keydown',e=>{if((e.metaKey||e.ctrlKey)&&e.key==='k'){e.preventDefault();pal?.classList.toggle('open');}if(e.key==='Escape')pal?.classList.remove('open');});
UI Patterns
#347 CSS + JS

Scroll-Triggered Nav Color

Nav background changes from transparent to solid after scrolling 100px.

Attribute
data-nav-scroll
CSS
[data-nav-scroll]{transition:background 0.3s,box-shadow 0.3s;}[data-nav-scroll].solid{background:var(--bg,#fff);box-shadow:0 2px 16px rgba(0,0,0,0.1);}
JS JavaScript
const nav=document.querySelector('[data-nav-scroll]');window.addEventListener('scroll',()=>nav?.classList.toggle('solid',scrollY>100),{passive:true});
Navigation
#348 JS Only

Service Worker Registration

Registers a service worker for offline caching on page load.

Attribute
data-register-sw
JS JavaScript
if('serviceWorker' in navigator){window.addEventListener('load',()=>navigator.serviceWorker.register('/sw.js').catch(()=>{}));}
Performance
#349 CSS Only

Dyslexia Friendly Font

Applies OpenDyslexic font to improve readability for dyslexic users.

Attribute
data-dyslexia
CSS
@import url('https://fonts.cdnfonts.com/css/opendyslexic');[data-dyslexia] *{font-family:'OpenDyslexic',sans-serif!important;letter-spacing:0.05em;line-height:1.6;}
Accessibility
#350 JS Only

Particle Explosion on Click

Spawns outward flying square particles from the click point.

Attribute
data-explode
JS JavaScript
document.querySelectorAll('[data-explode]').forEach(el=>{el.addEventListener('click',e=>{for(let i=0;i<20;i++){const p=document.createElement('div');const angle=Math.random()*360;const d=Math.random()*60+30;Object.assign(p.style,{position:'fixed',left:e.clientX+'px',top:e.clientY+'px',width:'6px',height:'6px',borderRadius:'1px',background:'#6366f1',pointerEvents:'none',zIndex:'9999',transition:'all 0.5s ease-out',opacity:'1'});document.body.appendChild(p);setTimeout(()=>{p.style.transform=`translate(${Math.cos(angle*Math.PI/180)*d}px,${Math.sin(angle*Math.PI/180)*d}px) scale(0)`;p.style.opacity='0';},10);setTimeout(()=>p.remove(),600);}});});
Interaction
#351 CSS Only

Spring Scale

Element scales up with a spring overshoot on hover.

Attribute
data-spring
CSS
[data-spring]{transition:transform 0.4s cubic-bezier(.34,1.56,.64,1);}[data-spring]:hover{transform:scale(1.1);}
Animations
#352 CSS Only

Flip Card Vertical

Card flips on the horizontal axis to reveal its back on hover.

Attribute
data-flip-v
CSS
[data-flip-v]{perspective:800px;}[data-flip-v] .front,[data-flip-v] .back{backface-visibility:hidden;transition:transform 0.6s ease;}[data-flip-v] .back{transform:rotateX(180deg);}[data-flip-v]:hover .front{transform:rotateX(-180deg);}[data-flip-v]:hover .back{transform:rotateX(0);}
Animations
#353 CSS Only

Shimmer Text

A shimmer highlight sweeps across text continuously.

Attribute
data-shimmer-text
CSS
[data-shimmer-text]{background:linear-gradient(90deg,#6366f1 30%,#a5b4fc 50%,#6366f1 70%);background-size:200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;animation:shimText 2s linear infinite;}@keyframes shimText{to{background-position:200%;}}
Animations
#354 CSS + JS

Fade In Up Sequence

Each list item fades up sequentially with increasing delay.

Attribute
data-seq-fade
CSS
[data-seq-fade]>*{opacity:0;transform:translateY(16px);transition:opacity 0.45s,transform 0.45s;}[data-seq-fade].ready>*{opacity:1;transform:none;}
JS JavaScript
document.querySelectorAll('[data-seq-fade]').forEach(el=>{[...el.children].forEach((c,i)=>(c as HTMLElement).style.transitionDelay=i*0.07+'s');new IntersectionObserver(([e])=>{if(e.isIntersecting)el.classList.add('ready');}).observe(el);});
Animations
#355 CSS Only

Breathing Glow

Element pulses with a glowing shadow rhythm like breathing.

Attribute
data-breathe
CSS
[data-breathe]{animation:breatheGlow 3s ease-in-out infinite;}@keyframes breatheGlow{0%,100%{box-shadow:0 0 8px rgba(99,102,241,0.3);}50%{box-shadow:0 0 32px rgba(99,102,241,0.8);}}
Animations
#356 CSS Only

Elastic Underline

Underline stretches elastically on hover beneath nav links.

Attribute
data-elastic-link
CSS
[data-elastic-link]{position:relative;text-decoration:none;}[data-elastic-link]::after{content:'';position:absolute;bottom:0;left:50%;width:0;height:2px;background:#6366f1;transition:width 0.35s cubic-bezier(.34,1.56,.64,1),left 0.35s cubic-bezier(.34,1.56,.64,1);}[data-elastic-link]:hover::after{left:0;width:100%;}
Animations
#357 CSS + JS

Table of Contents Highlighter

Highlights the TOC link for the currently visible heading.

Attribute
data-toc
CSS
[data-toc] a{transition:color 0.2s;}[data-toc] a.active{color:#6366f1;font-weight:700;}
JS JavaScript
const obs=new IntersectionObserver(es=>es.forEach(e=>{const id=e.target.id;const link=document.querySelector(`[data-toc] a[href="#${id}"]`);link?.classList.toggle('active',e.isIntersecting);}),{rootMargin:'-10% 0px -80% 0px'});document.querySelectorAll('h2[id],h3[id]').forEach(h=>obs.observe(h));
Scroll
#358 CSS Only

Parallax Background Image

Background image moves at a slower rate than the page scroll for depth.

Attribute
data-parallax-bg
CSS
[data-parallax-bg]{background-attachment:fixed;background-size:cover;background-position:center;}
Scroll
#359 CSS Only

Scroll Snap Carousel

A horizontal scroll snap carousel with smooth item snapping.

Attribute
data-snap-carousel
CSS
[data-snap-carousel]{display:flex;overflow-x:scroll;scroll-snap-type:x mandatory;gap:1rem;scrollbar-width:none;}[data-snap-carousel]::-webkit-scrollbar{display:none;}[data-snap-carousel]>*{scroll-snap-align:start;flex-shrink:0;}
Scroll
#360 JS Only

Scroll to Top on Hash

Smooth-scrolls to the element matching the URL hash on page load.

Attribute
data-hash-scroll
JS JavaScript
if(location.hash){const el=document.querySelector(location.hash);setTimeout(()=>el?.scrollIntoView({behavior:'smooth',block:'start'}),300);}
Scroll
#361 JS Only

Cursor Color Match

Cursor dot color interpolates with the background color under it.

Attribute
data-cursor-match
JS JavaScript
const dot=document.querySelector<HTMLElement>('[data-cursor-dot]');if(dot){document.addEventListener('mousemove',e=>{const els=document.elementsFromPoint(e.clientX,e.clientY);const bg=els.find(el=>el!==dot&&getComputedStyle(el).backgroundColor!=='rgba(0, 0, 0, 0)');if(bg){const c=getComputedStyle(bg).backgroundColor;dot.style.background=c;}});}
Cursor
#362 CSS Only

Uppercase on Scroll

Text animates from lowercase to uppercase as it enters the viewport.

Attribute
data-case-scroll
CSS
[data-case-scroll]{animation:caseChange linear both;animation-timeline:view();animation-range:entry 0% entry 30%;}@keyframes caseChange{from{text-transform:lowercase;}to{text-transform:uppercase;}}
Text
#363 CSS + JS

Strikethrough Reveal

A strikethrough line draws across text then reveals new text beneath.

Attribute
data-strike-reveal
CSS
[data-strike-reveal]{position:relative;}[data-strike-reveal]::after{content:'';position:absolute;top:50%;left:0;height:2px;width:0;background:#ef4444;transition:width 0.5s ease 0.3s;}[data-strike-reveal].go::after{width:100%;}
JS JavaScript
new IntersectionObserver(([e])=>{if(e.isIntersecting)e.target.classList.add('go');}).observe(document.querySelector('[data-strike-reveal]')!);
Text
#364 CSS Only

Pullquote Highlight Bar

Adds a decorative left border bar to styled blockquote pullquotes.

Attribute
data-pullquote
CSS
[data-pullquote]{border-left:4px solid #6366f1;padding-left:1.25rem;font-size:1.25rem;font-style:italic;color:rgba(255,255,255,0.75);}
Text
#365 CSS Only

Scanline Overlay

Adds a retro CRT scanline effect over any element.

Attribute
data-scanlines
CSS
[data-scanlines]::after{content:'';position:absolute;inset:0;background:repeating-linear-gradient(0deg,rgba(0,0,0,0.08) 0px,rgba(0,0,0,0.08) 1px,transparent 1px,transparent 2px);pointer-events:none;border-radius:inherit;}[data-scanlines]{position:relative;}
Visual FX
#366 CSS Only

Vignette Overlay

Adds a radial vignette darkening around the edges of an element.

Attribute
data-vignette
CSS
[data-vignette]{position:relative;}[data-vignette]::after{content:'';position:absolute;inset:0;background:radial-gradient(ellipse at center,transparent 55%,rgba(0,0,0,0.6) 100%);pointer-events:none;border-radius:inherit;}
Visual FX
#367 CSS Only

Pixel Art Filter

Applies a pixelate effect using CSS image-rendering.

Attribute
data-pixel
CSS
[data-pixel]{image-rendering:pixelated;image-rendering:crisp-edges;filter:contrast(1.1);}
Visual FX
#368 CSS Only

Duotone Image

Applies a two-color duotone treatment to an image using CSS filters.

Attribute
data-duotone
CSS
[data-duotone]{filter:contrast(1.3) brightness(0.9) sepia(0.7) hue-rotate(200deg);}
Visual FX
#369 CSS Only

Frosted Blur Behind

Child element gets a frosted glass appearance with backdrop-filter.

Attribute
data-frost
CSS
[data-frost]{backdrop-filter:blur(20px) brightness(1.1);-webkit-backdrop-filter:blur(20px) brightness(1.1);background:rgba(255,255,255,0.06);border-radius:12px;}
Visual FX
#370 JS Only

Matrix Rain Text

Rains green Matrix-style characters in a canvas overlay.

Attribute
data-matrix
CSS
[data-matrix]{position:relative;}[data-matrix] canvas{position:absolute;top:0;left:0;pointer-events:none;opacity:0.35;}
JS JavaScript
document.querySelectorAll('[data-matrix]').forEach(el=>{const c=document.createElement('canvas');el.appendChild(c);const ctx=c.getContext('2d')!;c.width=el.clientWidth;c.height=el.clientHeight;const cols=Math.floor(c.width/14);const drops=Array(cols).fill(1);setInterval(()=>{ctx.fillStyle='rgba(0,0,0,0.05)';ctx.fillRect(0,0,c.width,c.height);ctx.fillStyle='#22c55e';ctx.font='13px monospace';drops.forEach((y,i)=>{ctx.fillText(String.fromCharCode(Math.random()*94+33),i*14,y*14);drops[i]=y*14>c.height&&Math.random()>0.97?0:y+1;});},50);});
Visual FX
#371 CSS + JS

Swipeable Card Stack

Stack of cards that can be swiped away to reveal the next.

Attribute
data-card-stack
CSS
[data-card-stack]>*{position:absolute;transition:transform 0.35s,opacity 0.35s;}[data-card-stack]>*:nth-child(1){transform:translateY(0) scale(1);z-index:3;}[data-card-stack]>*:nth-child(2){transform:translateY(8px) scale(0.97);z-index:2;}[data-card-stack]>*:nth-child(3){transform:translateY(16px) scale(0.94);z-index:1;}
JS JavaScript
document.querySelectorAll('[data-card-stack]').forEach(stack=>{stack.addEventListener('click',()=>{const first=stack.children[0];first.classList.add('swiped');setTimeout(()=>{first.remove();stack.appendChild(first);first.classList.remove('swiped');},350);});});
UI Patterns
#372 CSS Only

Fixed Aspect Image Grid

A photo gallery grid where all images maintain the same aspect ratio.

Attribute
data-img-grid
CSS
[data-img-grid]{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem;}[data-img-grid] img{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:8px;}
UI Patterns
#373 JS Only

Animated Number Ticker

Ticks a number up or down to a target value with smooth animation.

Attribute
data-ticker
JS JavaScript
document.querySelectorAll('[data-ticker]').forEach(el=>{const to=parseFloat(el.dataset.ticker||'0');const from=parseFloat(el.dataset.tickerFrom||'0');const dur=parseInt(el.dataset.tickerDur||'1000');let start:number|null=null;const step=(ts:number)=>{if(!start)start=ts;const p=Math.min((ts-start)/dur,1);el.textContent=String(Math.round(from+(to-from)*p));if(p<1)requestAnimationFrame(step);};requestAnimationFrame(step);});
UI Patterns
#374 CSS + JS

Expandable Card

Card expands to show more content with a smooth animated transition.

Attribute
data-expand-card
CSS
[data-expand-card] .card-extra{display:grid;grid-template-rows:0fr;transition:grid-template-rows 0.4s;}[data-expand-card] .card-extra>*{overflow:hidden;}[data-expand-card].open .card-extra{grid-template-rows:1fr;}
JS JavaScript
document.querySelectorAll('[data-expand-card]').forEach(card=>{card.addEventListener('click',()=>card.classList.toggle('open'));});
UI Patterns
#375 CSS + JS

Color Swatch Picker

Visual color swatches that update a hidden input and a preview div.

Attribute
data-swatch-picker
CSS
[data-swatch]{width:28px;height:28px;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:border-color 0.2s,transform 0.2s;}[data-swatch].selected{border-color:#fff;transform:scale(1.2);}
JS JavaScript
document.querySelectorAll('[data-swatch-picker]').forEach(picker=>{picker.querySelectorAll<HTMLElement>('[data-swatch]').forEach(sw=>{sw.addEventListener('click',()=>{picker.querySelectorAll('[data-swatch]').forEach(s=>s.classList.remove('selected'));sw.classList.add('selected');picker.dispatchEvent(new CustomEvent('swatch-change',{detail:sw.dataset.swatchColor||sw.style.background}));});});});
UI Patterns
#376 CSS + JS

OTP Input

Auto-advances focus across 4 or 6 single-digit OTP inputs.

Attribute
data-otp
CSS
[data-otp]{display:flex;gap:8px;}[data-otp] input{width:44px;height:52px;text-align:center;font-size:1.3rem;border:1px solid rgba(255,255,255,0.2);border-radius:8px;background:rgba(255,255,255,0.05);color:#fff;}
JS JavaScript
document.querySelectorAll('[data-otp]').forEach(group=>{const inputs=[...group.querySelectorAll<HTMLInputElement>('input')];inputs.forEach((inp,i)=>{inp.addEventListener('input',()=>{if(inp.value&&i<inputs.length-1)inputs[i+1].focus();});inp.addEventListener('keydown',e=>{if(e.key==='Backspace'&&!inp.value&&i>0)inputs[i-1].focus();});});});
Forms
#377 JS Only

Scroll-to-Error on Submit

On form submit failure, smoothly scrolls to and focuses the first invalid field.

Attribute
data-scroll-error
JS JavaScript
document.querySelectorAll('[data-scroll-error]').forEach(form=>{form.addEventListener('submit',e=>{const invalid=form.querySelector(':invalid') as HTMLElement|null;if(invalid){e.preventDefault();invalid.scrollIntoView({behavior:'smooth',block:'center'});setTimeout(()=>invalid.focus(),400);}});});
Forms
#378 CSS Only

Mosaic Layout

CSS grid mosaic with some cells spanning multiple rows or columns.

Attribute
data-mosaic
CSS
[data-mosaic]{display:grid;grid-template-columns:repeat(3,1fr);grid-auto-rows:200px;gap:1rem;}[data-mosaic] [data-span-2c]{grid-column:span 2;}[data-mosaic] [data-span-2r]{grid-row:span 2;}[data-mosaic] [data-span-both]{grid-column:span 2;grid-row:span 2;}
Layout
#379 CSS Only

Responsive Nav to Bottom Bar

Converts a desktop horizontal nav into a mobile bottom bar.

Attribute
data-adaptive-nav
CSS
[data-adaptive-nav]{display:flex;gap:1rem;}@media(max-width:640px){[data-adaptive-nav]{position:fixed;bottom:0;left:0;width:100%;justify-content:space-around;padding:0.75rem 1rem;background:var(--bg,#fff);border-top:1px solid rgba(255,255,255,0.1);z-index:100;}}
Layout
#380 JS Only

Critical CSS Inliner

Moves critical above-the-fold styles inline to eliminate render-blocking.

Attribute
data-critical-css
JS JavaScript
document.querySelectorAll('[data-critical-css]').forEach(link=>{fetch(link.getAttribute('href')||'').then(r=>r.text()).then(css=>{const style=document.createElement('style');style.textContent=css;link.replaceWith(style);});});
Performance
#381 JS Only

Async Font Load

Loads a Google Font asynchronously to avoid render-blocking.

Attribute
data-async-font
JS JavaScript
document.querySelectorAll('[data-async-font]').forEach(el=>{const url=el.dataset.asyncFont||'';const link=document.createElement('link');link.rel='stylesheet';link.href=url;link.media='print';link.onload=()=>link.media='all';document.head.appendChild(link);});
Performance
#382 CSS + JS

ARIA Expanded Toggle

Toggles aria-expanded on a button and shows/hides its target.

Attribute
data-aria-toggle
CSS
[data-aria-target]{display:none;}[data-aria-target].expanded{display:block;}
JS JavaScript
document.querySelectorAll('[data-aria-toggle]').forEach(btn=>{btn.addEventListener('click',()=>{const isExp=btn.getAttribute('aria-expanded')==='true';btn.setAttribute('aria-expanded',String(!isExp));const target=document.getElementById(btn.dataset.ariaToggle||'');target?.classList.toggle('expanded',!isExp);});});
Accessibility
#383 JS Only

Keyboard Navigation Arrows

Lets users navigate a list of items with arrow keys.

Attribute
data-arrow-nav
JS JavaScript
document.querySelectorAll('[data-arrow-nav]').forEach(list=>{const items=[...list.querySelectorAll<HTMLElement>('[role=option],[data-nav-item]')];list.addEventListener('keydown',e=>{const i=items.indexOf(document.activeElement as HTMLElement);if(e.key==='ArrowDown'){e.preventDefault();items[Math.min(i+1,items.length-1)]?.focus();}if(e.key==='ArrowUp'){e.preventDefault();items[Math.max(i-1,0)]?.focus();}});});
Accessibility
#384 CSS Only

Responsive Video

Video element scales to fill its container while maintaining aspect ratio.

Attribute
data-resp-video
CSS
[data-resp-video]{width:100%;height:auto;display:block;}
Media
#385 JS Only

Autoplay on Hover Video

Video starts playing when the user hovers and pauses on mouse leave.

Attribute
data-hover-video
JS JavaScript
document.querySelectorAll('[data-hover-video]').forEach(video=>{const v=video as HTMLVideoElement;v.muted=true;video.addEventListener('mouseenter',()=>v.play());video.addEventListener('mouseleave',()=>v.pause());});
Media
#386 CSS + JS

Drag & Drop Kanban

Kanban columns that accept card drops and reorder on drag.

Attribute
data-kanban
CSS
[data-kanban]{display:flex;gap:1rem;}[data-kanban-col]{flex:1;min-height:100px;padding:0.5rem;border-radius:8px;background:rgba(255,255,255,0.04);}[data-kanban-col].drag-over{background:rgba(99,102,241,0.1);}
JS JavaScript
document.querySelectorAll('[data-kanban-col]').forEach(col=>{col.addEventListener('dragover',e=>{e.preventDefault();col.classList.add('drag-over');});col.addEventListener('dragleave',()=>col.classList.remove('drag-over'));col.addEventListener('drop',e=>{e.preventDefault();col.classList.remove('drag-over');const id=(e as DragEvent).dataTransfer?.getData('text/plain');const card=document.getElementById(id||'');if(card)col.appendChild(card);});});document.querySelectorAll('[data-kanban-card]').forEach(card=>{(card as HTMLElement).draggable=true;(card as HTMLElement).addEventListener('dragstart',e=>(e as DragEvent).dataTransfer?.setData('text/plain',card.id));});
Interaction
#387 CSS + JS

Keyboard Hint Display

Shows relevant keyboard shortcuts in a floating hint box when Ctrl is held.

Attribute
data-keyboard-hints
CSS
[data-kb-hints]{position:fixed;bottom:1rem;left:50%;transform:translateX(-50%);background:#1a1a2e;border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:0.75rem 1.25rem;display:none;gap:1rem;z-index:9999;}[data-kb-hints].show{display:flex;}
JS JavaScript
const box=document.querySelector('[data-kb-hints]');document.addEventListener('keydown',e=>{if(e.key==='Control')box?.classList.add('show');});document.addEventListener('keyup',e=>{if(e.key==='Control')box?.classList.remove('show');});
Interaction
#388 JS Only

Hover Intent Delay

Only fires hover logic after cursor has rested on element for 200ms.

Attribute
data-hover-intent
JS JavaScript
document.querySelectorAll('[data-hover-intent]').forEach(el=>{let t:any;el.addEventListener('mouseenter',()=>t=setTimeout(()=>el.classList.add('intent-active'),200));el.addEventListener('mouseleave',()=>{clearTimeout(t);el.classList.remove('intent-active');});});
Interaction
#389 CSS + JS

Live Background Gradient Mouse

Background gradient follows mouse position across the page.

Attribute
data-mouse-gradient
CSS
body{background:radial-gradient(circle 600px at var(--mx,50%) var(--my,50%),rgba(99,102,241,0.18),transparent 70%);}
JS JavaScript
document.addEventListener('mousemove',e=>{document.documentElement.style.setProperty('--mx',e.clientX+'px');document.documentElement.style.setProperty('--my',e.clientY+'px');});
Color & Theme
#390 CSS Only

Sunset Gradient Cycle

Background cycles through a warm sunset palette on a timed loop.

Attribute
data-sunset
CSS
[data-sunset]{animation:sunsetCycle 12s ease-in-out infinite;}@keyframes sunsetCycle{0%{background:linear-gradient(180deg,#ff6b6b,#feca57);}33%{background:linear-gradient(180deg,#ff9ff3,#f368e0);}66%{background:linear-gradient(180deg,#5f27cd,#341f97);}100%{background:linear-gradient(180deg,#ff6b6b,#feca57);}}
Color & Theme
#391 CSS + JS

Age Gate Modal

Asks users to confirm they are 18+ before showing age-restricted content.

Attribute
data-age-gate
CSS
[data-age-gate]{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:9999;align-items:center;justify-content:center;}[data-age-gate].show{display:flex;}
JS JavaScript
const gate=document.querySelector('[data-age-gate]');if(!sessionStorage.getItem('age-ok'))gate?.classList.add('show');document.querySelector('[data-age-confirm]')?.addEventListener('click',()=>{sessionStorage.setItem('age-ok','1');gate?.classList.remove('show');});
Misc
#392 CSS + JS

Cookie Consent Banner

Displays a GDPR-style consent banner until the user accepts.

Attribute
data-cookie-banner
CSS
[data-cookie-banner]{position:fixed;bottom:0;left:0;right:0;background:#1a1a2e;padding:1rem 2rem;display:flex;align-items:center;justify-content:space-between;z-index:9999;transform:translateY(0);transition:transform 0.3s;}[data-cookie-banner].hidden{transform:translateY(100%);}
JS JavaScript
const banner=document.querySelector('[data-cookie-banner]');if(localStorage.getItem('cookies-ok'))banner?.classList.add('hidden');document.querySelector('[data-cookie-accept]')?.addEventListener('click',()=>{localStorage.setItem('cookies-ok','1');banner?.classList.add('hidden');});
Misc
#393 CSS + JS

Exit Intent Popup

Triggers a popup when the cursor leaves the top of the viewport.

Attribute
data-exit-intent
CSS
[data-exit-popup]{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.75);z-index:9999;align-items:center;justify-content:center;}[data-exit-popup].show{display:flex;}
JS JavaScript
let fired=false;document.addEventListener('mouseleave',e=>{if(e.clientY<=0&&!fired){fired=true;document.querySelector('[data-exit-popup]')?.classList.add('show');}});document.querySelector('[data-exit-close]')?.addEventListener('click',()=>document.querySelector('[data-exit-popup]')?.classList.remove('show'));
Misc
#394 JS Only

Auto Save Draft

Auto-saves textarea content to localStorage every 5 seconds.

Attribute
data-autosave
JS JavaScript
document.querySelectorAll('[data-autosave]').forEach(el=>{const key='autosave-'+el.id;if(localStorage.getItem(key))(el as HTMLTextAreaElement).value=localStorage.getItem(key)||'';setInterval(()=>localStorage.setItem(key,(el as HTMLTextAreaElement).value),5000);});
Misc
#395 CSS + JS

Search Autocomplete

Shows a dropdown of matching suggestions as the user types in a search input.

Attribute
data-autocomplete
CSS
[data-ac-list]{position:absolute;top:100%;left:0;right:0;background:#1a1a2e;border:1px solid rgba(255,255,255,0.1);border-radius:8px;z-index:100;display:none;}[data-ac-list].show{display:block;}[data-ac-item]{padding:0.5rem 0.75rem;cursor:pointer;}[data-ac-item]:hover{background:rgba(99,102,241,0.15);}
JS JavaScript
document.querySelectorAll('[data-autocomplete]').forEach(input=>{const list=input.parentElement?.querySelector('[data-ac-list]');const items=input.parentElement?.querySelectorAll('[data-ac-item]');if(!list||!items)return;input.addEventListener('input',()=>{const q=(input as HTMLInputElement).value.toLowerCase();let any=false;items.forEach(item=>{const match=item.textContent?.toLowerCase().includes(q)&&q.length>0;(item as HTMLElement).style.display=match?'':'none';if(match)any=true;});list.classList.toggle('show',any);});document.addEventListener('click',e=>{if(!input.contains(e.target as Node))list.classList.remove('show');});items.forEach(item=>item.addEventListener('click',()=>{(input as HTMLInputElement).value=item.textContent||'';list.classList.remove('show');}));});
UI Patterns
#396 CSS Only

Skeleton Avatar Group

Shows stacked avatar skeleton placeholders during load.

Attribute
data-avatar-group
CSS
[data-avatar-group]{display:flex;}[data-avatar-group] .avatar{width:36px;height:36px;border-radius:50%;border:2px solid var(--bg,#111);margin-left:-10px;background:linear-gradient(90deg,#333 25%,#444 50%,#333 75%);background-size:200%;animation:shimmer 1.5s infinite;}[data-avatar-group] .avatar:first-child{margin-left:0;}
UI Patterns
#397 CSS + JS

Floating Action Button

A fixed circular FAB with a + icon that expands to show sub-actions.

Attribute
data-fab
CSS
[data-fab]{position:fixed;bottom:2rem;right:2rem;width:56px;height:56px;border-radius:50%;background:#6366f1;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:9000;box-shadow:0 4px 20px rgba(99,102,241,0.5);transition:transform 0.3s;}[data-fab]:hover{transform:scale(1.1) rotate(45deg);}[data-fab-menu]{position:fixed;bottom:6rem;right:2rem;display:flex;flex-direction:column;gap:0.5rem;z-index:8999;opacity:0;pointer-events:none;transition:opacity 0.3s;}[data-fab-menu].open{opacity:1;pointer-events:auto;}
JS JavaScript
const fab=document.querySelector('[data-fab]');const menu=document.querySelector('[data-fab-menu]');fab?.addEventListener('click',()=>menu?.classList.toggle('open'));
UI Patterns
#398 CSS + JS

Side Navigation Dots

Thin side dots link to page sections; active dot is highlighted.

Attribute
data-side-dots
CSS
[data-side-dots]{position:fixed;right:1.5rem;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:10px;z-index:100;}.side-dot{width:8px;height:8px;border-radius:50%;background:rgba(255,255,255,0.2);cursor:pointer;transition:background 0.25s,transform 0.25s;}.side-dot.active{background:#6366f1;transform:scale(1.5);}
JS JavaScript
const dots=document.querySelector('[data-side-dots]');const sections=document.querySelectorAll('[data-side-section]');sections.forEach((sec,i)=>{const d=document.createElement('span');d.className='side-dot';d.addEventListener('click',()=>sec.scrollIntoView({behavior:'smooth'}));dots?.appendChild(d);});const obs=new IntersectionObserver(es=>es.forEach(e=>{const i=[...sections].indexOf(e.target as HTMLElement);dots?.querySelectorAll('.side-dot')[i]?.classList.toggle('active',e.isIntersecting);}),{threshold:0.5});sections.forEach(s=>obs.observe(s));
Navigation
#399 CSS Only

Pointer-only Hover Guard

Only applies hover styles when primary input is a mouse/pointer, not touch.

Attribute
data-pointer-hover
CSS
@media(hover:hover) and (pointer:fine){[data-pointer-hover]:hover{opacity:0.8;}}
Accessibility
#400 CSS Only

Touch Target Enlarger

Expands the tap target of small icons using padding and negative margin.

Attribute
data-touch-target
CSS
[data-touch-target]{padding:12px;margin:-12px;display:inline-flex;align-items:center;justify-content:center;}
Accessibility
#401 CSS + JS

Countdown Timer

Counts down from a given seconds value and fires an event at zero.

Attribute
data-countdown
CSS
[data-countdown]{font-variant-numeric:tabular-nums;}
JS JavaScript
document.querySelectorAll('[data-countdown]').forEach(el=>{let sec=parseInt(el.dataset.countdown||'60');const tick=()=>{el.textContent=String(sec).padStart(2,'0');if(sec-->0)setTimeout(tick,1000);else el.dispatchEvent(new Event('countdown-end'));};tick();});
Animations
#402 CSS Only

Gradient Border Pulse

Border pulses with a glowing gradient breathing animation.

Attribute
data-border-pulse
CSS
[data-border-pulse]{border:2px solid transparent;border-radius:12px;background-clip:padding-box;box-shadow:0 0 0 2px #6366f1;animation:borderPulse 2s ease-in-out infinite;}@keyframes borderPulse{0%,100%{box-shadow:0 0 0 2px rgba(99,102,241,0.4);}50%{box-shadow:0 0 0 6px rgba(99,102,241,0.8);}}
Animations
#403 CSS Only

Morph Icon

SVG icon morphs from one path to another on hover using CSS path animation.

Attribute
data-morph-icon
CSS
[data-morph-icon] path{transition:d 0.4s cubic-bezier(.34,1.56,.64,1);}
Animations
#404 CSS + JS

Scroll Jacking Section

Full-screen section hijacks wheel scroll to transition between internal slides.

Attribute
data-scroll-jack
CSS
[data-scroll-jack]{overflow:hidden;height:100vh;}[data-scroll-jack] [data-jack-slide]{height:100vh;transition:transform 0.6s cubic-bezier(.4,0,.2,1);}
JS JavaScript
document.querySelectorAll('[data-scroll-jack]').forEach(el=>{let idx=0;const slides=[...el.querySelectorAll<HTMLElement>('[data-jack-slide]')];el.addEventListener('wheel',e=>{e.preventDefault();idx=Math.min(Math.max(idx+(e.deltaY>0?1:-1),0),slides.length-1);slides.forEach((s,i)=>s.style.transform=`translateY(${(i-idx)*100}%)`);},{passive:false});});
Scroll
#405 CSS Only

Timeline Component

Renders a vertical timeline with icon nodes and connecting lines.

Attribute
data-timeline
CSS
[data-timeline]{position:relative;padding-left:2rem;}[data-timeline]::before{content:'';position:absolute;left:0.4rem;top:0;bottom:0;width:2px;background:rgba(255,255,255,0.08);}[data-timeline-item]::before{content:'';position:absolute;left:-1.6rem;top:0.2rem;width:12px;height:12px;border-radius:50%;background:#6366f1;border:2px solid #0a0a0c;}[data-timeline-item]{position:relative;padding-bottom:1.5rem;}
UI Patterns
#406 CSS Only

Comparison Table

Styled feature comparison table with check/cross icons.

Attribute
data-compare-table
CSS
[data-compare-table] td[data-yes]::before{content:'✓';color:#22c55e;font-weight:700;}[data-compare-table] td[data-no]::before{content:'✗';color:#ef4444;font-weight:700;}[data-compare-table] th{text-align:center;padding:0.75rem;}
UI Patterns
#407 CSS Only

Date Picker Highlight

Highlights today's date and selected date in a custom date input.

Attribute
data-date-pick
CSS
[data-date-pick]::-webkit-calendar-picker-indicator{filter:invert(1);}[data-date-pick]{background:rgba(99,102,241,0.08);border:1px solid rgba(99,102,241,0.3);border-radius:8px;padding:0.5rem 0.75rem;color:#fff;}
Forms
#408 JS Only

Checkbox Group Select All

Adds a master checkbox that toggles all checkboxes in a group.

Attribute
data-check-all
JS JavaScript
document.querySelectorAll('[data-check-all]').forEach(master=>{const group=document.querySelectorAll<HTMLInputElement>('[data-check-group="'+master.id+'"]');(master as HTMLInputElement).addEventListener('change',()=>group.forEach(c=>c.checked=(master as HTMLInputElement).checked));group.forEach(c=>c.addEventListener('change',()=>(master as HTMLInputElement).checked=[...group].every(x=>x.checked)));});
Forms
#409 JS Only

Passive Event Listeners

Marks scroll and touch events as passive for improved scrolling performance.

Attribute
data-passive
JS JavaScript
document.querySelectorAll('[data-passive]').forEach(el=>{['scroll','touchstart','touchmove'].forEach(ev=>el.addEventListener(ev,()=>{},{passive:true}));});
Performance
#410 JS Only

Bundle Splitting Hint

Dynamically imports a heavy module only when an element is interacted with.

Attribute
data-lazy-module
JS JavaScript
document.querySelectorAll('[data-lazy-module]').forEach(el=>{let loaded=false;el.addEventListener('click',async()=>{if(loaded)return;loaded=true;const mod=el.dataset.lazyModule||'';await import(mod).catch(()=>{});});});
Performance
#411 JS Only

Role Description

Adds role and aria-roledescription to make custom widgets accessible.

Attribute
data-role-desc="Slider"
JS JavaScript
document.querySelectorAll('[data-role-desc]').forEach(el=>{el.setAttribute('role','group');el.setAttribute('aria-roledescription',el.dataset.roleDesc||'');});
Accessibility
#412 CSS + JS

360 Image Viewer

Drag to rotate through frames of a 360° product image sequence.

Attribute
data-360
CSS
[data-360]{cursor:grab;user-select:none;}[data-360].dragging{cursor:grabbing;}
JS JavaScript
document.querySelectorAll('[data-360]').forEach(el=>{const frames=el.dataset['360']?.split(',').map(s=>s.trim())||[];let idx=0,down=false,startX=0;el.addEventListener('mousedown',e=>{down=true;startX=e.clientX;el.classList.add('dragging');});document.addEventListener('mouseup',()=>{down=false;el.classList.remove('dragging');});el.addEventListener('mousemove',e=>{if(!down)return;const dx=e.clientX-startX;if(Math.abs(dx)>10){idx=(idx+Math.sign(dx)+frames.length)%frames.length;(el as HTMLImageElement).src=frames[idx];startX=e.clientX;}});});
Media
#413 JS Only

Multi-select Checkboxes with Shift

Holding Shift selects a range of checkboxes at once.

Attribute
data-range-select
JS JavaScript
let lastChecked:HTMLInputElement|null=null;document.querySelectorAll<HTMLInputElement>('[data-range-select] input[type=checkbox]').forEach(cb=>{cb.addEventListener('click',e=>{if(e.shiftKey&&lastChecked){const boxes=[...document.querySelectorAll<HTMLInputElement>('[data-range-select] input[type=checkbox]')];const a=boxes.indexOf(lastChecked),b=boxes.indexOf(cb);boxes.slice(Math.min(a,b),Math.max(a,b)+1).forEach(x=>x.checked=cb.checked);}lastChecked=cb;});});
Interaction
#414 JS Only

CSS Palette from Image

Extracts dominant colors from an image and sets them as CSS variables.

Attribute
data-palette-from-img
JS JavaScript
document.querySelectorAll('[data-palette-from-img]').forEach(img=>{const canvas=document.createElement('canvas');canvas.width=canvas.height=1;canvas.getContext('2d')?.drawImage(img as HTMLImageElement,0,0,1,1);const[r,g,b]=canvas.getContext('2d')?.getImageData(0,0,1,1).data||[99,102,241];document.documentElement.style.setProperty('--img-accent',`rgb(${r},${g},${b})`);});
Color & Theme
#415 CSS + JS

Offline Page Message

Shows a message overlay when the user loses internet connection.

Attribute
data-offline-msg
CSS
[data-offline-msg]{display:none;position:fixed;top:0;left:0;right:0;background:#ef4444;color:#fff;text-align:center;padding:0.5rem;z-index:9999;}[data-offline-msg].show{display:block;}
JS JavaScript
const msg=document.querySelector('[data-offline-msg]');window.addEventListener('offline',()=>msg?.classList.add('show'));window.addEventListener('online',()=>msg?.classList.remove('show'));if(!navigator.onLine)msg?.classList.add('show');
Misc
#416 CSS + JS

Survey Rating Scale

NPS-style 0–10 rating scale that highlights up to the hovered/selected score.

Attribute
data-nps
CSS
[data-nps]{display:flex;gap:4px;}[data-nps] button{width:36px;height:36px;border-radius:6px;border:1px solid rgba(255,255,255,0.1);background:transparent;color:#fff;cursor:pointer;transition:background 0.15s;}[data-nps] button.selected,[data-nps] button:hover~button~button{background:none;}[data-nps] button:hover,[data-nps] button.fill{background:#6366f1;}
JS JavaScript
document.querySelectorAll('[data-nps]').forEach(group=>{let sel=-1;const btns=[...group.querySelectorAll<HTMLButtonElement>('button')];btns.forEach((btn,i)=>{btn.addEventListener('mouseenter',()=>btns.forEach((b,j)=>b.classList.toggle('fill',j<=i)));btn.addEventListener('mouseleave',()=>btns.forEach((b,j)=>b.classList.toggle('fill',j<=sel)));btn.addEventListener('click',()=>{sel=i;group.dispatchEvent(new CustomEvent('nps-select',{detail:i}));});});});
Misc
#417 CSS Only

Liquid Loader

Animated SVG-style liquid fill loader bar.

Attribute
data-liquid-loader
CSS
[data-liquid-loader]{height:6px;border-radius:3px;background:rgba(255,255,255,0.1);overflow:hidden;}[data-liquid-loader]::after{content:'';display:block;height:100%;background:linear-gradient(90deg,#6366f1,#ec4899);border-radius:3px;animation:liquidLoad 2s ease-in-out infinite;width:40%;}@keyframes liquidLoad{0%{transform:translateX(-100%);}100%{transform:translateX(350%);}}
Animations
#418 JS Only

Number Rain

Animated falling numbers matrix-style.

Attribute
data-num-rain
CSS
[data-num-rain]{position:relative;overflow:hidden;}
JS JavaScript
document.querySelectorAll('[data-num-rain]').forEach(el=>{for(let i=0;i<15;i++){const s=document.createElement('span');s.textContent=String(Math.floor(Math.random()*10));Object.assign(s.style,{position:'absolute',top:'-1.5rem',left:Math.random()*100+'%',color:'rgba(99,102,241,0.5)',fontSize:'0.9rem',fontFamily:'monospace',animation:`numFall ${Math.random()*3+2}s linear ${Math.random()*3}s infinite`});el.appendChild(s);}const style=document.createElement('style');style.textContent='@keyframes numFall{to{transform:translateY(120%);opacity:0;}}';document.head.appendChild(style);});
Animations
#419 CSS Only

Section Entrance Direction

Detects if a section enters from below or above, applying matching animation.

Attribute
data-dir-enter
CSS
[data-dir-enter].from-bottom{animation:slideUp linear both;animation-timeline:view();animation-range:entry 0% entry 25%;}[data-dir-enter].from-top{animation:slideDown linear both;animation-timeline:view();animation-range:exit 0% exit 25%;}@keyframes slideDown{to{opacity:0;transform:translateY(-30px);}}
Scroll
#420 JS Only

Scroll Position Saver

Saves and restores scroll position in localStorage on page reload.

Attribute
data-save-scroll
JS JavaScript
window.addEventListener('beforeunload',()=>localStorage.setItem('scroll-pos',String(scrollY)));window.addEventListener('load',()=>{const y=localStorage.getItem('scroll-pos');if(y)window.scrollTo(0,parseInt(y));});
Scroll
#421 CSS Only

Marquee Logos Row

Infinite scrolling logo row for partner/client displays.

Attribute
data-logo-marquee
CSS
[data-logo-marquee]{overflow:hidden;white-space:nowrap;}[data-logo-marquee] .track{display:inline-flex;gap:2rem;animation:logoScroll 20s linear infinite;}[data-logo-marquee]:hover .track{animation-play-state:paused;}@keyframes logoScroll{to{transform:translateX(-50%);}}
UI Patterns
#422 CSS Only

Alert Banner Types

Styled alert banners for info, success, warning, and error states.

Attribute
data-alert="success"
CSS
[data-alert]{padding:0.75rem 1.25rem;border-radius:8px;border-left:4px solid;}[data-alert="success"]{background:rgba(34,197,94,0.1);border-color:#22c55e;color:#86efac;}[data-alert="error"]{background:rgba(239,68,68,0.1);border-color:#ef4444;color:#fca5a5;}[data-alert="warning"]{background:rgba(245,158,11,0.1);border-color:#f59e0b;color:#fcd34d;}[data-alert="info"]{background:rgba(59,130,246,0.1);border-color:#3b82f6;color:#93c5fd;}
UI Patterns
#423 CSS Only

Fluid Columns with Gap

CSS columns with fluid widths and consistent gap using calc.

Attribute
data-fluid-cols
CSS
[data-fluid-cols]{display:grid;grid-template-columns:repeat(auto-fit,minmax(calc(var(--col-min,220px)),1fr));gap:var(--col-gap,1.5rem);}
Layout
#424 JS Only

Form Dirty State Warning

Warns users before leaving the page if they have unsaved form changes.

Attribute
data-dirty-guard
JS JavaScript
document.querySelectorAll('[data-dirty-guard]').forEach(form=>{let dirty=false;form.addEventListener('input',()=>dirty=true);form.addEventListener('submit',()=>dirty=false);window.addEventListener('beforeunload',e=>{if(dirty){e.preventDefault();e.returnValue='';}});});
Forms
#425 CSS + JS

Intersection Fade Images

Images fade in only when they enter the viewport, reducing initial paint.

Attribute
data-fade-img
CSS
[data-fade-img]{opacity:0;transition:opacity 0.5s;}[data-fade-img].visible{opacity:1;}
JS JavaScript
new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){e.target.classList.add('visible');}}),{threshold:0.1}).observe(document.querySelector('[data-fade-img]')!);
Performance
#426 JS Only

Focus Lock on Open Drawer

Prevents focus from moving outside an open drawer/side panel.

Attribute
data-focus-lock
JS JavaScript
document.querySelectorAll('[data-focus-lock]').forEach(drawer=>{drawer.addEventListener('keydown',e=>{if(e.key!=='Tab')return;const all=[...drawer.querySelectorAll<HTMLElement>('a,button,input,[tabindex]:not([tabindex="-1"])')];if(!all.length)return;if(e.shiftKey&&document.activeElement===all[0]){e.preventDefault();all[all.length-1].focus();}else if(!e.shiftKey&&document.activeElement===all[all.length-1]){e.preventDefault();all[0].focus();}});});
Accessibility
#427 CSS + JS

Lazy Load Background

Sets background-image from data-src only when the element is visible.

Attribute
data-bg-lazy
CSS
[data-bg-lazy]{background-size:cover;background-position:center;transition:opacity 0.5s;opacity:0;}[data-bg-lazy].loaded{opacity:1;}
JS JavaScript
new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){(e.target as HTMLElement).style.backgroundImage=`url(${(e.target as HTMLElement).dataset.bgLazy})`;e.target.classList.add('loaded');}})).observe(document.querySelector('[data-bg-lazy]')!);
Media
#428 JS Only

Proximity Effect

Nearby elements move away from the cursor as if repelled by a magnet.

Attribute
data-repel
JS JavaScript
document.addEventListener('mousemove',e=>{document.querySelectorAll('[data-repel]').forEach(el=>{const r=el.getBoundingClientRect();const dx=e.clientX-(r.left+r.width/2);const dy=e.clientY-(r.top+r.height/2);const dist=Math.hypot(dx,dy);if(dist<80){const force=(80-dist)/80;(el as HTMLElement).style.transform=`translate(${-dx*force*0.5}px,${-dy*force*0.5}px)`;}else{(el as HTMLElement).style.transform='';}});});
Interaction
#429 CSS + JS

Invert Mode

Inverts all colors for a quick negative/dark toggle.

Attribute
data-invert
CSS
html.inverted{filter:invert(1) hue-rotate(180deg);}html.inverted img,html.inverted video{filter:invert(1) hue-rotate(180deg);}
JS JavaScript
document.querySelector('[data-invert]')?.addEventListener('click',()=>document.documentElement.classList.toggle('inverted'));
Color & Theme
#430 JS Only

Confetti on Form Submit

Fires confetti particles when a form is successfully submitted.

Attribute
data-submit-confetti
JS JavaScript
document.querySelectorAll('[data-submit-confetti]').forEach(form=>{form.addEventListener('submit',()=>{for(let i=0;i<60;i++){const p=document.createElement('span');const colors=['#6366f1','#ec4899','#22c55e','#f59e0b'];Object.assign(p.style,{position:'fixed',top:Math.random()*100+'vh',left:Math.random()*100+'vw',width:'8px',height:'8px',borderRadius:'2px',background:colors[Math.floor(Math.random()*colors.length)],pointerEvents:'none',zIndex:'9999',animation:'confettiFall 1.5s ease-out forwards'});document.body.appendChild(p);setTimeout(()=>p.remove(),1600);}const s=document.createElement('style');s.textContent='@keyframes confettiFall{to{transform:translateY(80px) rotate(360deg);opacity:0;}}';document.head.appendChild(s);});});
Misc
#431 CSS + JS

Letter by Letter Reveal

Each letter of a heading appears one by one with a stagger.

Attribute
data-letter-reveal
CSS
[data-letter-reveal] span{display:inline-block;opacity:0;transform:translateY(12px);transition:opacity 0.35s,transform 0.35s;}[data-letter-reveal].go span{opacity:1;transform:none;}
JS JavaScript
document.querySelectorAll('[data-letter-reveal]').forEach(el=>{el.innerHTML=[...el.textContent||''].map((c,i)=>`<span style="transition-delay:${i*0.04}s">${c==' '?'&nbsp;':c}</span>`).join('');new IntersectionObserver(([e])=>{if(e.isIntersecting)el.classList.add('go');}).observe(el);});
Animations
#432 CSS Only

Trail Blur Motion

Element leaves a motion blur trail as it animates across the screen.

Attribute
data-trail-blur
CSS
[data-trail-blur]{animation:trailMove 2s ease-in-out infinite alternate;}@keyframes trailMove{from{transform:translateX(0);filter:blur(0);}50%{filter:blur(6px);}to{transform:translateX(60px);filter:blur(0);}}
Animations
#433 CSS + JS

Masonry Grid with JS

True masonry layout computed with JS column heights.

Attribute
data-js-masonry
CSS
[data-js-masonry]{position:relative;}[data-js-masonry]>*{position:absolute;}
JS JavaScript
function layout(el:HTMLElement){const cols=Math.floor(el.offsetWidth/260)||1;const h=Array(cols).fill(0);const gap=16;[...el.children].forEach((child:Element,i)=>{const col=h.indexOf(Math.min(...h));const c=child as HTMLElement;c.style.left=col*(260+gap)+'px';c.style.top=h[col]+'px';c.style.width='260px';h[col]+=c.offsetHeight+gap;});el.style.height=Math.max(...h)+'px';}document.querySelectorAll<HTMLElement>('[data-js-masonry]').forEach(el=>{layout(el);window.addEventListener('resize',()=>layout(el));});
UI Patterns
#434 CSS + JS

Reveal on Scroll with Threshold

Custom threshold percentage for when scroll-reveal triggers (0–1).

Attribute
data-reveal-threshold="0.3"
CSS
[data-reveal-threshold]{opacity:0;transform:translateY(20px);transition:opacity 0.5s,transform 0.5s;}[data-reveal-threshold].revealed{opacity:1;transform:none;}
JS JavaScript
document.querySelectorAll('[data-reveal-threshold]').forEach(el=>{const t=parseFloat(el.dataset.revealThreshold||'0.2');new IntersectionObserver(([e])=>{if(e.isIntersecting)el.classList.add('revealed');},{threshold:t}).observe(el);});
Scroll
#435 CSS Only

Highlight on Select

Changes the text selection color to match the brand accent color.

Attribute
data-custom-select
CSS
[data-custom-select]::selection{background:rgba(99,102,241,0.4);color:#fff;}
Text
#436 CSS Only

Reading Highlight Bar

Highlights the current line of text the mouse is hovering on.

Attribute
data-read-highlight
CSS
[data-read-highlight] p:hover{background:rgba(99,102,241,0.08);border-radius:4px;}
Text
#437 CSS Only

CRT TV Effect

Applies CRT screen curve, scanlines, and flicker to an element.

Attribute
data-crt
CSS
[data-crt]{border-radius:4% / 3%;overflow:hidden;position:relative;}[data-crt]::after{content:'';position:absolute;inset:0;background:repeating-linear-gradient(0deg,rgba(0,0,0,0.06) 0px,rgba(0,0,0,0.06) 1px,transparent 1px,transparent 3px);pointer-events:none;}[data-crt]::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse at center,transparent 60%,rgba(0,0,0,0.5) 100%);pointer-events:none;z-index:1;}
Visual FX
#438 CSS Only

Neon Border Card

Card with animated neon color-shifting border.

Attribute
data-neon-border
CSS
[data-neon-border]{border:2px solid #6366f1;border-radius:12px;box-shadow:0 0 10px #6366f1,inset 0 0 10px rgba(99,102,241,0.1);animation:neonFlicker 3s ease-in-out infinite;}@keyframes neonFlicker{0%,100%{box-shadow:0 0 10px #6366f1,inset 0 0 10px rgba(99,102,241,0.1);}50%{box-shadow:0 0 24px #6366f1,0 0 48px #6366f1,inset 0 0 20px rgba(99,102,241,0.15);}}
Visual FX
#439 CSS Only

Table Row Highlight on Hover

Entire table row highlights on hover for improved readability.

Attribute
data-row-hover
CSS
[data-row-hover] tbody tr{transition:background 0.15s;}[data-row-hover] tbody tr:hover{background:rgba(99,102,241,0.08);}
UI Patterns
#440 CSS Only

Sticky Bottom CTA Bar

A sticky call-to-action bar docked to the bottom of the screen.

Attribute
data-sticky-cta
CSS
[data-sticky-cta]{position:fixed;bottom:0;left:0;right:0;padding:1rem 2rem;background:var(--bg,#fff);border-top:1px solid rgba(255,255,255,0.08);z-index:500;display:flex;align-items:center;justify-content:space-between;}
UI Patterns
#441 CSS Only

Sticky Footer Push

Pushes the footer to the bottom of the viewport even with little content.

Attribute
data-sticky-footer
CSS
[data-sticky-footer]{display:flex;flex-direction:column;min-height:100vh;}[data-sticky-footer] footer{margin-top:auto;}
Layout
#442 CSS Only

Overflow Scroll Shadow

Shows fade shadows at scroll edges to indicate there's more content.

Attribute
data-overflow-shadow
CSS
[data-overflow-shadow]{background:linear-gradient(to right,var(--bg,#fff) 0%,transparent 5%,transparent 95%,var(--bg,#fff) 100%) no-repeat,scroll;background-size:100% 100%;}
Layout
#443 CSS + JS

Credit Card Visual Preview

Real-time visual card preview that updates number/name as the user types.

Attribute
data-card-preview
CSS
[data-card-preview]{background:linear-gradient(135deg,#6366f1,#ec4899);border-radius:16px;padding:1.5rem;color:#fff;font-family:monospace;position:relative;}
JS JavaScript
const num=document.querySelector<HTMLInputElement>('[data-card-num]');const name=document.querySelector<HTMLInputElement>('[data-card-name]');const dispNum=document.querySelector('[data-card-preview-num]');const dispName=document.querySelector('[data-card-preview-name]');num?.addEventListener('input',()=>{if(dispNum)dispNum.textContent=num.value.replace(/(d{4})/g,'$1 ').trim()||'•••• •••• •••• ••••';});name?.addEventListener('input',()=>{if(dispName)dispName.textContent=name.value.toUpperCase()||'YOUR NAME';});
Forms
#444 CSS Only

Font Display Swap

Sets font-display:swap in @font-face to avoid invisible text during load.

Attribute
data-font-swap
CSS
@font-face{font-family:'CustomFont';src:url('/fonts/custom.woff2') format('woff2');font-display:swap;}
Performance
#445 CSS Only

Color Blind Assist

Adds patterns to colored elements to differentiate them without relying on color.

Attribute
data-colorblind
CSS
[data-colorblind] .success{background-image:repeating-linear-gradient(45deg,rgba(255,255,255,0.1) 0px,rgba(255,255,255,0.1) 2px,transparent 2px,transparent 8px);}[data-colorblind] .error{background-image:repeating-linear-gradient(-45deg,rgba(255,255,255,0.1) 0px,rgba(255,255,255,0.1) 2px,transparent 2px,transparent 8px);}
Accessibility
#446 CSS + JS

Podcast Player Progress

Custom audio player with progress bar and time display.

Attribute
data-podcast
CSS
[data-podcast-progress]{height:4px;background:rgba(255,255,255,0.1);border-radius:2px;cursor:pointer;}[data-podcast-fill]{height:100%;background:#6366f1;border-radius:2px;width:0%;transition:width 0.2s;}
JS JavaScript
document.querySelectorAll('[data-podcast]').forEach(audio=>{const fill=audio.parentElement?.querySelector<HTMLElement>('[data-podcast-fill]');const time=audio.parentElement?.querySelector('[data-podcast-time]');(audio as HTMLAudioElement).addEventListener('timeupdate',()=>{const a=audio as HTMLAudioElement;if(fill)fill.style.width=(a.currentTime/a.duration*100)+'%';if(time)time.textContent=new Date(a.currentTime*1000).toISOString().substr(14,5);});});
Media
#447 JS Only

Undo Last Action

Ctrl+Z undoes the last recorded DOM change via a command stack.

Attribute
data-undoable
JS JavaScript
const stack:Array<{el:HTMLElement,html:string}>=[];document.querySelectorAll('[data-undoable]').forEach(el=>{el.addEventListener('input',()=>stack.push({el:el as HTMLElement,html:(el as HTMLElement).innerHTML}));});document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='z'){const last=stack.pop();if(last)last.el.innerHTML=last.html;}});
Interaction
#448 CSS + JS

Pinned Tooltip Follow

A tooltip sticks to and follows an element as it animates on screen.

Attribute
data-pin-tip
CSS
[data-pin-tip-box]{position:fixed;pointer-events:none;background:#1a1a2e;padding:4px 10px;border-radius:6px;font-size:0.75rem;color:#fff;z-index:9999;opacity:0;transition:opacity 0.2s;}
JS JavaScript
document.querySelectorAll('[data-pin-tip]').forEach(el=>{const box=document.createElement('div');box.setAttribute('data-pin-tip-box','');box.textContent=el.dataset.pinTip||'';document.body.appendChild(box);const upd=()=>{const r=el.getBoundingClientRect();box.style.left=r.left+r.width/2-box.offsetWidth/2+'px';box.style.top=r.top-box.offsetHeight-8+'px';};el.addEventListener('mouseenter',()=>{upd();box.style.opacity='1';});el.addEventListener('mousemove',upd);el.addEventListener('mouseleave',()=>box.style.opacity='0');});
Interaction
#449 CSS Only

Themed Scrollbar

Styles the browser scrollbar to match the brand theme.

Attribute
data-themed-scroll
CSS
[data-themed-scroll]{scrollbar-width:thin;scrollbar-color:#6366f1 rgba(255,255,255,0.05);}[data-themed-scroll]::-webkit-scrollbar{width:8px;}[data-themed-scroll]::-webkit-scrollbar-track{background:rgba(255,255,255,0.03);}[data-themed-scroll]::-webkit-scrollbar-thumb{background:#6366f1;border-radius:4px;}
Color & Theme
#450 JS Only

Install PWA Prompt

Shows a custom install button when the browser fires the beforeinstallprompt event.

Attribute
data-pwa-install
CSS
[data-pwa-install]{display:none;}[data-pwa-install].show{display:inline-flex;}
JS JavaScript
let deferredPrompt:any;window.addEventListener('beforeinstallprompt',e=>{e.preventDefault();deferredPrompt=e;document.querySelector('[data-pwa-install]')?.classList.add('show');});document.querySelector('[data-pwa-install]')?.addEventListener('click',()=>{deferredPrompt?.prompt();});
Misc
#451 CSS Only

Slide In from Bottom

Element slides up into view from below on scroll entry.

Attribute
data-animate="slide-bottom"
CSS
[data-animate="slide-bottom"]{opacity:0;transform:translateY(60px);animation:slideFromBottom linear both;animation-timeline:view();animation-range:entry 0% entry 30%;}@keyframes slideFromBottom{to{opacity:1;transform:none;}}
Animations
#452 CSS Only

Rubber Band

Element stretches and snaps back like a rubber band on hover.

Attribute
data-rubber
CSS
[data-rubber]:hover{animation:rubberBand 0.7s ease;}@keyframes rubberBand{0%,100%{transform:scale(1,1);}30%{transform:scale(1.25,0.75);}40%{transform:scale(0.75,1.25);}50%{transform:scale(1.15,0.85);}65%{transform:scale(0.95,1.05);}75%{transform:scale(1.05,0.95);}}
Animations
#453 CSS Only

Tada

Element shakes and scales with a tada celebration animation.

Attribute
data-tada
CSS
[data-tada]:hover{animation:tada 0.8s ease;}@keyframes tada{0%,100%{transform:scale(1) rotate(0);}10%,20%{transform:scale(0.9) rotate(-3deg);}30%,50%,70%,90%{transform:scale(1.1) rotate(3deg);}40%,60%,80%{transform:scale(1.1) rotate(-3deg);}}
Animations
#454 CSS Only

Jello

Element jiggles like jelly on interaction.

Attribute
data-jello
CSS
[data-jello]:hover{animation:jello 0.9s ease both;}@keyframes jello{0%,100%{transform:skewX(0) skewY(0);}30%{transform:skewX(-12.5deg) skewY(-12.5deg);}40%{transform:skewX(6.25deg) skewY(6.25deg);}50%{transform:skewX(-3.125deg) skewY(-3.125deg);}65%{transform:skewX(1.5deg) skewY(1.5deg);}75%{transform:skewX(-0.75deg) skewY(-0.75deg);}}
Animations
#455 CSS Only

Light Speed In

Element zooms in from the right with a skew for a fast entry.

Attribute
data-lightspeed
CSS
[data-lightspeed]{opacity:0;animation:lightSpeedIn linear both;animation-timeline:view();animation-range:entry 0% entry 20%;}@keyframes lightSpeedIn{from{transform:translateX(100%) skewX(-30deg);opacity:0;}60%{transform:skewX(20deg);opacity:1;}80%{transform:skewX(-5deg);}to{transform:none;opacity:1;}}
Animations
#456 CSS Only

Scroll-linked Font Weight

Font weight increases as the element scrolls into center view.

Attribute
data-fw-scroll
CSS
[data-fw-scroll]{animation:fwScroll linear both;animation-timeline:view();animation-range:entry 0% cover 50%;}@keyframes fwScroll{from{font-weight:100;}to{font-weight:900;}}
Scroll
#457 CSS Only

Overflow Fade Edges

Long scrollable container shows fade masks at left and right edges.

Attribute
data-scroll-fade-edges
CSS
[data-scroll-fade-edges]{overflow-x:auto;mask-image:linear-gradient(to right,transparent,black 5%,black 95%,transparent);-webkit-mask-image:linear-gradient(to right,transparent,black 5%,black 95%,transparent);}
Scroll
#458 CSS Only

Auto-scroll Carousel

Carousel auto-scrolls left indefinitely with CSS animation.

Attribute
data-auto-scroll
CSS
[data-auto-scroll]{display:flex;overflow:hidden;}[data-auto-scroll] .track{display:flex;gap:1rem;animation:autoScroll 20s linear infinite;}[data-auto-scroll]:hover .track{animation-play-state:paused;}@keyframes autoScroll{to{transform:translateX(-50%);}}
Scroll
#459 CSS + JS

Cursor Glow

A soft glow aura follows the cursor across the page.

Attribute
data-cursor-glow
CSS
[data-cursor-glow]{width:300px;height:300px;border-radius:50%;background:radial-gradient(circle,rgba(99,102,241,0.15),transparent 70%);position:fixed;pointer-events:none;z-index:9998;transform:translate(-50%,-50%);transition:left 0.05s,top 0.05s;}
JS JavaScript
const g=document.querySelector<HTMLElement>('[data-cursor-glow]');document.addEventListener('mousemove',e=>{if(g){g.style.left=e.clientX+'px';g.style.top=e.clientY+'px';}});
Cursor
#460 CSS + JS

Cursor Text Label

Shows a custom text label next to the cursor when hovering a zone.

Attribute
data-cursor-label
CSS
[data-cursor-label-el]{position:fixed;pointer-events:none;font-size:0.75rem;background:#6366f1;color:#fff;padding:3px 8px;border-radius:20px;z-index:9999;opacity:0;transition:opacity 0.2s;}
JS JavaScript
const lbl=document.createElement('div');lbl.setAttribute('data-cursor-label-el','');document.body.appendChild(lbl);document.querySelectorAll('[data-cursor-label]').forEach(el=>{el.addEventListener('mouseenter',()=>{lbl.textContent=el.dataset.cursorLabel||'';lbl.style.opacity='1';});el.addEventListener('mousemove',e=>{lbl.style.left=e.clientX+14+'px';lbl.style.top=e.clientY-10+'px';});el.addEventListener('mouseleave',()=>lbl.style.opacity='0');});
Cursor
#461 CSS Only

Inline Code Style

Styles inline code snippets with a pill background.

Attribute
data-inline-code
CSS
[data-inline-code]{background:rgba(99,102,241,0.12);color:#818cf8;border-radius:5px;padding:0.1em 0.45em;font-family:monospace;font-size:0.88em;border:1px solid rgba(99,102,241,0.2);}
Text
#462 CSS + JS

Truncate with Read More

Clamps text at 3 lines; a Read More link expands to full.

Attribute
data-read-more
CSS
[data-read-more] .rm-body{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}[data-read-more].expanded .rm-body{-webkit-line-clamp:unset;}
JS JavaScript
document.querySelectorAll('[data-read-more]').forEach(el=>{const btn=el.querySelector('[data-rm-btn]');btn?.addEventListener('click',()=>{el.classList.toggle('expanded');btn.textContent=el.classList.contains('expanded')?'Read less':'Read more';});});
Text
#463 JS Only

Animated Number Counter

Counts from start to end value when scrolled into view.

Attribute
data-num-counter
JS JavaScript
document.querySelectorAll('[data-num-counter]').forEach(el=>{const [from,to]=(el.dataset.numCounter||'0,100').split(',').map(Number);const dur=1200;let start=0;const obs=new IntersectionObserver(([e])=>{if(!e.isIntersecting)return;obs.disconnect();const t0=performance.now();const step=(t:number)=>{const p=Math.min((t-t0)/dur,1);el.textContent=String(Math.round(from+(to-from)*p));if(p<1)requestAnimationFrame(step);};requestAnimationFrame(step);});obs.observe(el);});
Text
#464 CSS + JS

Distortion Hover

SVG feTurbulence distortion ramps up on hover.

Attribute
data-distort
CSS
[data-distort]{filter:url('#distort-0');transition:filter 0.3s;}[data-distort]:hover{filter:url('#distort-1');}
JS JavaScript
['0','1'].forEach(id=>{const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');svg.style.cssText='position:absolute;width:0;height:0';svg.innerHTML=`<defs><filter id="distort-${id}"><feTurbulence type="turbulence" baseFrequency="${id==='0'?'0':'0.05'}" numOctaves="3" result="noise"/><feDisplacementMap in="SourceGraphic" in2="noise" scale="${id==='0'?'0':'20'}" xChannelSelector="R" yChannelSelector="G"/></filter></defs>`;document.body.appendChild(svg);});
Visual FX
#465 JS Only

Particle Background

Floating particles drift across a dark background section.

Attribute
data-particle-bg
CSS
[data-particle-bg]{position:relative;overflow:hidden;}
JS JavaScript
document.querySelectorAll('[data-particle-bg]').forEach(el=>{for(let i=0;i<40;i++){const p=document.createElement('span');const s=Math.random()*4+2;Object.assign(p.style,{position:'absolute',width:s+'px',height:s+'px',borderRadius:'50%',background:'rgba(99,102,241,0.4)',top:Math.random()*100+'%',left:Math.random()*100+'%',animation:`particleDrift ${Math.random()*10+8}s ease-in-out ${Math.random()*5}s infinite alternate`,pointerEvents:'none'});el.appendChild(p);}const style=document.createElement('style');style.textContent='@keyframes particleDrift{to{transform:translate('+Math.random()*60-30+'px,'+Math.random()*60-30+'px);opacity:0.2;}}';document.head.appendChild(style);});
Visual FX
#466 CSS Only

Bokeh Blur Background

Soft blurred circles create a bokeh-style decorative background.

Attribute
data-bokeh
CSS
[data-bokeh]{position:relative;overflow:hidden;}[data-bokeh]::before,[data-bokeh]::after{content:'';position:absolute;border-radius:50%;filter:blur(60px);pointer-events:none;}[data-bokeh]::before{width:300px;height:300px;background:rgba(99,102,241,0.3);top:-80px;left:-80px;}[data-bokeh]::after{width:200px;height:200px;background:rgba(236,72,153,0.25);bottom:-50px;right:-50px;}
Visual FX
#467 CSS Only

Laser Line Effect

A laser beam line sweeps across an element on hover.

Attribute
data-laser
CSS
[data-laser]{position:relative;overflow:hidden;}[data-laser]::after{content:'';position:absolute;top:0;left:-100%;width:60%;height:100%;background:linear-gradient(90deg,transparent,rgba(99,102,241,0.6),transparent);transform:skewX(-20deg);transition:left 0.5s ease;}[data-laser]:hover::after{left:150%;}
Visual FX
#468 CSS Only

Split Button

A button with a main action and a dropdown arrow for secondary actions.

Attribute
data-split-btn
CSS
[data-split-btn]{display:inline-flex;}[data-split-btn] .main{border-radius:8px 0 0 8px;padding:0.5rem 1rem;background:#6366f1;color:#fff;border:none;cursor:pointer;}[data-split-btn] .arrow{border-radius:0 8px 8px 0;padding:0.5rem 0.65rem;background:#4f52cc;color:#fff;border:none;cursor:pointer;border-left:1px solid rgba(255,255,255,0.2);}[data-split-btn] .dropdown{position:absolute;top:100%;right:0;background:#1a1a2e;border:1px solid rgba(255,255,255,0.1);border-radius:8px;display:none;}[data-split-btn]:hover .dropdown{display:block;}
UI Patterns
#469 CSS + JS

Drag to Resize Panel

A divider handle lets users drag to resize two side-by-side panels.

Attribute
data-resizable
CSS
[data-resizable]{display:flex;}[data-resize-handle]{width:6px;cursor:col-resize;background:rgba(255,255,255,0.08);flex-shrink:0;transition:background 0.2s;}[data-resize-handle]:hover{background:#6366f1;}
JS JavaScript
document.querySelectorAll('[data-resizable]').forEach(wrap=>{const handle=wrap.querySelector<HTMLElement>('[data-resize-handle]');const left=wrap.querySelector<HTMLElement>('[data-panel-left]');if(!handle||!left)return;let active=false;handle.addEventListener('mousedown',()=>active=true);document.addEventListener('mouseup',()=>active=false);document.addEventListener('mousemove',e=>{if(!active)return;const r=wrap.getBoundingClientRect();left.style.width=Math.max(100,e.clientX-r.left)+'px';});});
UI Patterns
#470 CSS + JS

Infinite Pagination

Replaces numbered pagination with a single Load More button.

Attribute
data-load-more
CSS
[data-load-more]{display:inline-flex;align-items:center;gap:8px;}[data-load-more].loading::after{content:'';width:14px;height:14px;border:2px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:spin 0.7s linear infinite;}
JS JavaScript
document.querySelectorAll('[data-load-more]').forEach(btn=>{btn.addEventListener('click',()=>{btn.classList.add('loading');btn.dispatchEvent(new CustomEvent('load-more-click',{bubbles:true}));});});
UI Patterns
#471 CSS + JS

Input Shake on Invalid

Input shakes when the user submits with an invalid value.

Attribute
data-shake-invalid
CSS
[data-shake-invalid].shake{animation:shake 0.5s cubic-bezier(.36,.07,.19,.97);}@keyframes shake{10%,90%{transform:translateX(-1px);}20%,80%{transform:translateX(2px);}30%,50%,70%{transform:translateX(-4px);}40%,60%{transform:translateX(4px);}}
JS JavaScript
document.querySelectorAll('[data-shake-invalid]').forEach(inp=>{inp.closest('form')?.addEventListener('submit',e=>{if(!(inp as HTMLInputElement).checkValidity()){e.preventDefault();inp.classList.remove('shake');void (inp as HTMLElement).offsetWidth;inp.classList.add('shake');}});});
Forms
#472 CSS + JS

Password Show/Hide Toggle

Eye icon toggles password visibility inside the input field.

Attribute
data-pw-toggle
CSS
[data-pw-wrap]{position:relative;}[data-pw-eye]{position:absolute;right:0.75rem;top:50%;transform:translateY(-50%);cursor:pointer;opacity:0.5;}[data-pw-eye]:hover{opacity:1;}
JS JavaScript
document.querySelectorAll('[data-pw-toggle]').forEach(inp=>{const btn=document.createElement('button');btn.setAttribute('data-pw-eye','');btn.type='button';btn.innerHTML='👁';inp.parentElement?.appendChild(btn);btn.addEventListener('click',()=>{(inp as HTMLInputElement).type=(inp as HTMLInputElement).type==='password'?'text':'password';});});
Forms
#473 CSS + JS

Keyboard Tab Navigation

Users can switch tabs with arrow keys for full keyboard accessibility.

Attribute
data-tab-nav
CSS
[data-tab-nav] [role=tab]{cursor:pointer;}[data-tab-nav] [role=tab][aria-selected=true]{border-bottom:2px solid #6366f1;color:#6366f1;}
JS JavaScript
document.querySelectorAll('[data-tab-nav]').forEach(nav=>{const tabs=[...nav.querySelectorAll<HTMLElement>('[role=tab]')];tabs.forEach((tab,i)=>{tab.addEventListener('keydown',e=>{if(e.key==='ArrowRight'){tabs[(i+1)%tabs.length].focus();tabs[(i+1)%tabs.length].click();}if(e.key==='ArrowLeft'){tabs[(i-1+tabs.length)%tabs.length].focus();tabs[(i-1+tabs.length)%tabs.length].click();}});});});
Navigation
#474 CSS Only

Popover Menu

CSS-only popover menu using the new Popover API.

Attribute
data-popover-menu
CSS
[popover]{position:fixed;border:1px solid rgba(255,255,255,0.1);background:#1a1a2e;border-radius:10px;padding:0.5rem;margin:0;}
Navigation
#475 JS Only

Local Storage Form

Saves every input value to localStorage and restores on next visit.

Attribute
data-ls-form
JS JavaScript
document.querySelectorAll('[data-ls-form]').forEach(form=>{form.querySelectorAll<HTMLInputElement>('input,textarea,select').forEach(el=>{const k='lsf-'+el.name;if(localStorage.getItem(k))el.value=localStorage.getItem(k)||'';el.addEventListener('input',()=>localStorage.setItem(k,el.value));});});
Utility
#476 JS Only

URL Param Reader

Reads a URL search parameter and injects its value into an element.

Attribute
data-url-param
JS JavaScript
document.querySelectorAll('[data-url-param]').forEach(el=>{const val=new URLSearchParams(location.search).get(el.dataset.urlParam||'');if(val)el.textContent=decodeURIComponent(val);});
Utility
#477 CSS + JS

Table Sort

Clicking a table header column sorts rows by that column's values.

Attribute
data-sortable
CSS
[data-sortable] th{cursor:pointer;user-select:none;}[data-sortable] th::after{content:' ↕';opacity:0.4;}[data-sortable] th.asc::after{content:' ↑';opacity:1;}[data-sortable] th.desc::after{content:' ↓';opacity:1;}
JS JavaScript
document.querySelectorAll('[data-sortable]').forEach(table=>{const tbody=table.querySelector('tbody')!;table.querySelectorAll('th').forEach((th,i)=>{let asc=true;th.addEventListener('click',()=>{table.querySelectorAll('th').forEach(h=>h.className='');th.className=asc?'asc':'desc';const rows=[...tbody.rows];rows.sort((a,b)=>a.cells[i].textContent!.localeCompare(b.cells[i].textContent!,undefined,{numeric:true})*(asc?1:-1));rows.forEach(r=>tbody.appendChild(r));asc=!asc;});});});
Utility
#478 JS Only

Viewport Batch Observer

Groups IntersectionObserver callbacks for multiple elements into one observer.

Attribute
data-batch-observe
JS JavaScript
const batchObs=new IntersectionObserver(entries=>{entries.forEach(e=>{if(e.isIntersecting){e.target.dispatchEvent(new CustomEvent('in-view'));batchObs.unobserve(e.target);}});});document.querySelectorAll('[data-batch-observe]').forEach(el=>batchObs.observe(el));
Performance
#479 CSS + JS

Error Message Role

Injects role=alert on error messages so screen readers announce them immediately.

Attribute
data-error-msg
CSS
[data-error-msg]{color:#ef4444;font-size:0.8rem;margin-top:0.25rem;}
JS JavaScript
document.querySelectorAll('[data-error-msg]').forEach(el=>{el.setAttribute('role','alert');el.setAttribute('aria-live','assertive');});
Accessibility
#480 CSS Only

Visible Focus Indicator

Draws a bright 3px ring on any focused element for maximum visibility.

Attribute
data-a11y-focus
CSS
[data-a11y-focus] *:focus-visible{outline:3px solid #6366f1!important;outline-offset:3px!important;border-radius:4px;}
Accessibility
#481 JS Only

Picture-in-Picture Button

Adds a PiP button to video elements for floating playback.

Attribute
data-pip
JS JavaScript
document.querySelectorAll('[data-pip]').forEach(video=>{const btn=document.createElement('button');btn.textContent='PiP';btn.addEventListener('click',()=>(video as any).requestPictureInPicture?.());video.parentElement?.appendChild(btn);});
Media
#482 JS Only

Fullscreen Image on Click

Any image expands to fullscreen using the Fullscreen API on double-click.

Attribute
data-fs-img
JS JavaScript
document.querySelectorAll('[data-fs-img]').forEach(img=>{img.addEventListener('dblclick',()=>(img as any).requestFullscreen?.());});
Media
#483 CSS + JS

Click Counter

Counts and displays how many times an element has been clicked.

Attribute
data-click-count
CSS
[data-click-count]::after{content:' ('attr(data-clicks)')';opacity:0.5;font-size:0.8em;}
JS JavaScript
document.querySelectorAll('[data-click-count]').forEach(el=>{let n=0;el.addEventListener('click',()=>el.setAttribute('data-clicks',String(++n)));});
Interaction
#484 JS Only

Keyboard Type Sound

Plays a subtle click sound on every keypress inside an input.

Attribute
data-type-sound
JS JavaScript
document.querySelectorAll('[data-type-sound]').forEach(el=>{el.addEventListener('keydown',()=>{const ctx=new AudioContext();const o=ctx.createOscillator();const g=ctx.createGain();o.connect(g);g.connect(ctx.destination);o.frequency.value=800+Math.random()*200;g.gain.setValueAtTime(0.05,ctx.currentTime);g.gain.exponentialRampToValueAtTime(0.001,ctx.currentTime+0.08);o.start();o.stop(ctx.currentTime+0.08);});});
Interaction
#485 CSS Only

CSS Print Theme

Applies a print-friendly theme that removes backgrounds and adjusts colors.

Attribute
data-print-theme
CSS
@media print{[data-print-theme]{background:#fff!important;color:#000!important;}[data-print-theme] *{background:transparent!important;color:#000!important;box-shadow:none!important;}}
Color & Theme
#486 CSS Only

Monochrome Filter

Desaturates the entire page or a section to monochrome.

Attribute
data-mono
CSS
[data-mono]{filter:grayscale(1);}
Color & Theme
#487 JS Only

Notification Permission Request

Requests browser notification permission on button click.

Attribute
data-notif-request
JS JavaScript
document.querySelectorAll('[data-notif-request]').forEach(btn=>{btn.addEventListener('click',()=>Notification.requestPermission().then(p=>{btn.textContent=p==='granted'?'Notifications On':'Permission Denied';}));});
Misc
#488 JS Only

Geolocation Display

Displays the user's latitude and longitude on click.

Attribute
data-geolocation
JS JavaScript
document.querySelectorAll('[data-geolocation]').forEach(el=>{el.addEventListener('click',()=>navigator.geolocation.getCurrentPosition(p=>{el.textContent=`${p.coords.latitude.toFixed(4)}, ${p.coords.longitude.toFixed(4)}`;},()=>el.textContent='Denied'));});
Misc
#489 JS Only

Battery Status Badge

Shows device battery level and charging status.

Attribute
data-battery
JS JavaScript
(async()=>{const nav=navigator as any;if(!nav.getBattery)return;const b=await nav.getBattery();document.querySelectorAll('[data-battery]').forEach(el=>{const upd=()=>el.textContent=`${Math.round(b.level*100)}% ${b.charging?'⚡':''}`;upd();b.addEventListener('levelchange',upd);b.addEventListener('chargingchange',upd);});})();
Misc
#490 JS Only

Screen Wake Lock

Prevents the screen from sleeping while a section is visible.

Attribute
data-wake-lock
JS JavaScript
let wl:any=null;const obs=new IntersectionObserver(async([e])=>{if(e.isIntersecting&&!wl){try{wl=await(navigator as any).wakeLock?.request('screen');}catch{}}else if(!e.isIntersecting&&wl){wl.release();wl=null;}});document.querySelectorAll('[data-wake-lock]').forEach(el=>obs.observe(el));
Misc
#491 CSS + JS

Flip Number Ticker

Numbers flip like an airport departure board when value changes.

Attribute
data-flip-ticker
CSS
[data-flip-ticker]{display:inline-block;perspective:200px;}[data-flip-ticker] span{display:inline-block;transition:transform 0.4s ease;transform-origin:top center;}[data-flip-ticker].flip span{transform:rotateX(-90deg);}
JS JavaScript
document.querySelectorAll('[data-flip-ticker]').forEach(el=>{const update=(val:string)=>{el.classList.add('flip');setTimeout(()=>{el.querySelector('span')!.textContent=val;el.classList.remove('flip');},200);};el.dataset.flipFn='';(el as any).update=update;});
Animations
#492 JS Only

Bubble Up

Small bubble circles float upward from the bottom of an element.

Attribute
data-bubbles
CSS
[data-bubbles]{position:relative;overflow:hidden;}
JS JavaScript
document.querySelectorAll('[data-bubbles]').forEach(el=>{setInterval(()=>{const b=document.createElement('span');const s=Math.random()*20+8;Object.assign(b.style,{position:'absolute',bottom:'0',left:Math.random()*100+'%',width:s+'px',height:s+'px',borderRadius:'50%',background:'rgba(99,102,241,0.25)',pointerEvents:'none',animation:`bubbleFloat ${Math.random()*2+2}s ease-out forwards`});el.appendChild(b);setTimeout(()=>b.remove(),4000);},400);const s=document.createElement('style');s.textContent='@keyframes bubbleFloat{to{transform:translateY(-120%);opacity:0;}}';document.head.appendChild(s);});
Animations
#493 JS Only

Horizontal Scroll with Wheel

Converts vertical mouse wheel scroll to horizontal on a container.

Attribute
data-wheel-h
JS JavaScript
document.querySelectorAll('[data-wheel-h]').forEach(el=>{el.addEventListener('wheel',e=>{e.preventDefault();(el as HTMLElement).scrollLeft+=e.deltaY;},{passive:false});});
Scroll
#494 CSS + JS

Last Seen Section Badge

Shows a 'Continue reading' badge linking back to the last visited section.

Attribute
data-continue-read
CSS
[data-continue-badge]{position:fixed;bottom:1.5rem;left:1.5rem;background:#6366f1;color:#fff;border-radius:8px;padding:0.5rem 1rem;font-size:0.8rem;cursor:pointer;z-index:100;display:none;}[data-continue-badge].show{display:block;}
JS JavaScript
const badge=document.querySelector<HTMLElement>('[data-continue-badge]');const sections=document.querySelectorAll('[data-continue-read]');const obs=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){localStorage.setItem('last-section',e.target.id);if(badge){badge.textContent=`↩ Continue: ${(e.target as HTMLElement).dataset.continueRead}`;badge.classList.add('show');}}}),{threshold:0.3});sections.forEach(s=>obs.observe(s));badge?.addEventListener('click',()=>{const id=localStorage.getItem('last-section');document.getElementById(id||'')?.scrollIntoView({behavior:'smooth'});badge.classList.remove('show');});
Scroll
#495 CSS + JS

Inline Edit Text

Click any tagged text to edit it inline; blur saves the value.

Attribute
data-inline-edit
CSS
[data-inline-edit]{cursor:text;border-bottom:1px dashed rgba(255,255,255,0.2);}[data-inline-edit]:focus{outline:none;border-bottom-color:#6366f1;}
JS JavaScript
document.querySelectorAll('[data-inline-edit]').forEach(el=>{el.setAttribute('contenteditable','true');el.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();(el as HTMLElement).blur();}});el.addEventListener('blur',()=>el.dispatchEvent(new CustomEvent('inline-save',{detail:el.textContent,bubbles:true})));});
UI Patterns
#496 CSS Only

Hover Preview Card

Shows a detailed preview card on hover with image and text.

Attribute
data-preview-hover
CSS
[data-preview-hover]{position:relative;}[data-preview-box]{position:absolute;bottom:calc(100% + 8px);left:0;width:240px;background:#1a1a2e;border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:1rem;z-index:100;opacity:0;pointer-events:none;transform:translateY(6px);transition:opacity 0.2s,transform 0.2s;}[data-preview-hover]:hover [data-preview-box]{opacity:1;transform:none;pointer-events:auto;}
UI Patterns
#497 CSS Only

CSS Layers

Uses @layer to organize CSS specificity without !important hacks.

Attribute
data-layered
CSS
@layer base,components,utilities;@layer base{[data-layered]{font-family:inherit;}}@layer utilities{[data-layered]{padding:1rem;}}
Layout
#498 CSS Only

Two-Panel Debug Grid

Overlays a 12-column debug grid on the page for alignment checking.

Attribute
data-debug-grid
CSS
[data-debug-grid]{position:fixed;inset:0;display:grid;grid-template-columns:repeat(12,1fr);gap:1rem;padding:0 2rem;pointer-events:none;z-index:9999;}[data-debug-grid]>*{background:rgba(99,102,241,0.07);border:1px solid rgba(99,102,241,0.15);}
Layout
#499 JS Only

Idle Callback Defer

Defers non-critical JS work until the browser is idle.

Attribute
data-idle-defer
JS JavaScript
document.querySelectorAll('[data-idle-defer]').forEach(el=>{const fn=new Function(el.dataset.idleDefer||'');('requestIdleCallback' in window?(requestIdleCallback as Function):setTimeout)(fn);});
Performance
#500 CSS + JS

Fun Fact on Idle

Shows a rotating fun fact after the user is idle for 10 seconds.

Attribute
data-idle-fact
CSS
[data-idle-fact-popup]{position:fixed;bottom:2rem;right:2rem;max-width:260px;background:#1a1a2e;border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:1rem;font-size:0.82rem;opacity:0;transform:translateY(20px);transition:opacity 0.4s,transform 0.4s;z-index:9999;}[data-idle-fact-popup].show{opacity:1;transform:none;}
JS JavaScript
const facts=(document.querySelector('[data-idle-fact]') as HTMLElement|null)?.dataset.idleFact?.split('|')||['Did you know? CSS is Turing complete!'];let t:any;const pop=document.querySelector('[data-idle-fact-popup]');const reset=()=>{clearTimeout(t);t=setTimeout(()=>{if(pop){pop.textContent=facts[Math.floor(Math.random()*facts.length)];pop.classList.add('show');setTimeout(()=>pop.classList.remove('show'),5000);}},10000);};['click','keydown','mousemove'].forEach(ev=>document.addEventListener(ev,reset));reset();
Misc
#501 CSS Only

Elastic Button Press

Button squashes vertically on press and bounces back.

Attribute
data-press
CSS
[data-press]{transition:transform 0.1s;}[data-press]:active{transform:scaleY(0.88) scaleX(1.08);}
Animations
#502 CSS Only

Gradient Sweep Button

A gradient sweeps across a button background on hover.

Attribute
data-grad-sweep
CSS
[data-grad-sweep]{background-size:200% 100%;background-image:linear-gradient(90deg,#6366f1 0%,#ec4899 50%,#6366f1 100%);transition:background-position 0.4s;}[data-grad-sweep]:hover{background-position:-100% 0;}
Animations
#503 CSS + JS

Text Color Wave

Each letter of text cycles through a rainbow wave animation.

Attribute
data-color-wave
CSS
[data-color-wave] span{animation:colorWave 2s ease-in-out infinite;}@keyframes colorWave{0%,100%{color:#6366f1;}33%{color:#ec4899;}66%{color:#f59e0b;}}
JS JavaScript
document.querySelectorAll('[data-color-wave]').forEach(el=>{el.innerHTML=[...el.textContent||''].map((c,i)=>`<span style="animation-delay:${i*0.07}s">${c==' '?'&nbsp;':c}</span>`).join('');});
Animations
#504 CSS + JS

Chapter Progress Indicator

A thin bar per chapter fills as you read through that section.

Attribute
data-chapter-progress
CSS
[data-chapter-bar]{height:2px;background:rgba(255,255,255,0.08);}[data-chapter-fill]{height:100%;background:#6366f1;width:0%;transition:width 0.1s;}
JS JavaScript
document.querySelectorAll('[data-chapter-progress]').forEach(chapter=>{const fill=chapter.querySelector<HTMLElement>('[data-chapter-fill]');if(!fill)return;const obs=new IntersectionObserver(([e])=>{fill.style.width=e.intersectionRatio*100+'%';},{threshold:[...Array(101).keys()].map(n=>n/100)});obs.observe(chapter);});
Scroll
#505 CSS Only

Sticky Column Layout

Left column sticks while right column scrolls for editorial layouts.

Attribute
data-sticky-col
CSS
[data-sticky-col]{display:grid;grid-template-columns:1fr 1fr;gap:2rem;align-items:start;}[data-sticky-col]>[data-col-sticky]{position:sticky;top:1rem;}
Scroll
#506 CSS Only

Cursor Crosshair

Custom crosshair cursor for precision interfaces.

Attribute
data-crosshair
CSS
[data-crosshair]{cursor:crosshair;}
Cursor
#507 CSS + JS

Blurred Spoiler

Text is blurred until the user clicks to reveal it.

Attribute
data-spoiler
CSS
[data-spoiler]{filter:blur(6px);cursor:pointer;transition:filter 0.4s;user-select:none;}[data-spoiler].revealed{filter:none;}
JS JavaScript
document.querySelectorAll('[data-spoiler]').forEach(el=>el.addEventListener('click',()=>el.classList.toggle('revealed')));
Text
#508 CSS Only

Inline Definition Tooltip

Hovering over a term shows its definition in a tooltip.

Attribute
data-define="Your definition here"
CSS
[data-define]{text-decoration:underline dotted #6366f1;cursor:help;position:relative;}[data-define]::after{content:attr(data-define);position:absolute;bottom:calc(100% + 6px);left:0;background:#1a1a2e;color:#fff;padding:0.4em 0.8em;border-radius:8px;font-size:0.75rem;width:max-content;max-width:200px;opacity:0;pointer-events:none;transition:opacity 0.2s;z-index:10;}[data-define]:hover::after{opacity:1;}
Text
#509 CSS Only

Ripple Background

Concentric ripple rings emanate from the center of an element.

Attribute
data-ripple-bg
CSS
[data-ripple-bg]{position:relative;overflow:hidden;}[data-ripple-bg]::before{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:0;height:0;border-radius:50%;border:2px solid rgba(99,102,241,0.4);animation:rippleExpand 2s ease-out infinite;}[data-ripple-bg]::after{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:0;height:0;border-radius:50%;border:2px solid rgba(99,102,241,0.2);animation:rippleExpand 2s ease-out 0.7s infinite;}@keyframes rippleExpand{to{width:200%;height:200%;opacity:0;}}
Visual FX
#510 JS Only

Starburst Click

Starburst lines radiate outward from a button on click.

Attribute
data-starburst-click
JS JavaScript
document.querySelectorAll('[data-starburst-click]').forEach(el=>{el.addEventListener('click',e=>{for(let i=0;i<8;i++){const line=document.createElement('div');const angle=i*45;Object.assign(line.style,{position:'fixed',left:e.clientX+'px',top:e.clientY+'px',width:'2px',height:'2px',background:'#6366f1',transformOrigin:'0 0',transform:`rotate(${angle}deg)`,transition:'all 0.4s ease-out',pointerEvents:'none',zIndex:'9999'});document.body.appendChild(line);setTimeout(()=>{line.style.width='40px';line.style.opacity='0';},10);setTimeout(()=>line.remove(),500);}});});
Visual FX
#511 CSS + JS

Pill Toggle Group

Mutually exclusive pill toggles for filter or option selection.

Attribute
data-pill-group
CSS
[data-pill-group]{display:flex;gap:6px;flex-wrap:wrap;}[data-pill-group] button{padding:0.3rem 1rem;border-radius:20px;border:1px solid rgba(255,255,255,0.1);background:transparent;color:rgba(255,255,255,0.5);cursor:pointer;transition:all 0.2s;}[data-pill-group] button.active{background:#6366f1;border-color:#6366f1;color:#fff;}
JS JavaScript
document.querySelectorAll('[data-pill-group]').forEach(group=>{group.querySelectorAll('button').forEach(btn=>{btn.addEventListener('click',()=>{group.querySelectorAll('button').forEach(b=>b.classList.remove('active'));btn.classList.add('active');group.dispatchEvent(new CustomEvent('pill-change',{detail:btn.textContent}));});});});
UI Patterns
#512 CSS + JS

Chip Input Tag Remover

Renders existing tags as chips with an × to remove them.

Attribute
data-chip-list
CSS
[data-chip-list]{display:flex;flex-wrap:wrap;gap:6px;}.chip{display:inline-flex;align-items:center;gap:4px;background:rgba(99,102,241,0.15);border:1px solid rgba(99,102,241,0.3);color:#818cf8;border-radius:20px;padding:2px 10px;font-size:0.78rem;}.chip button{background:none;border:none;cursor:pointer;color:inherit;font-size:0.9em;padding:0;}
JS JavaScript
document.querySelectorAll('[data-chip-list]').forEach(el=>{el.querySelectorAll<HTMLElement>('[data-chip]').forEach(chip=>{const c=document.createElement('span');c.className='chip';c.innerHTML=chip.textContent+'<button>×</button>';c.querySelector('button')?.addEventListener('click',()=>c.remove());chip.replaceWith(c);});});
UI Patterns
#513 CSS Only

Print-Hidden Elements

Hides decorative or nav elements when printing.

Attribute
data-print-hide
CSS
@media print{[data-print-hide]{display:none!important;}}
Layout
#514 CSS Only

Safe Area Insets

Adds padding for notches/safe areas on modern mobile devices.

Attribute
data-safe-area
CSS
[data-safe-area]{padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom);padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right);}
Layout
#515 CSS Only

Input Loading State

Shows a spinner inside an input during async validation.

Attribute
data-input-loading
CSS
[data-input-loading].loading{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236366f1' stroke-width='2'%3E%3Cpath d='M21 12a9 9 0 1 1-6.219-8.56'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 0.75rem center;padding-right:2.5rem;animation:spinBg 0.8s linear infinite;}@keyframes spinBg{to{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236366f1' stroke-width='2'%3E%3Cpath d='M21 12a9 9 0 1 1-6.219-8.56'/%3E%3C/svg%3E");}}
Forms
#516 JS Only

Prerender Next Page

Prerenders the likely next page using the Speculation Rules API.

Attribute
data-prerender
JS JavaScript
if(HTMLScriptElement.supports?.('speculationrules')){const s=document.createElement('script');s.type='speculationrules';s.textContent=JSON.stringify({prerender:[{source:'list',urls:document.querySelectorAll<HTMLAnchorElement>('[data-prerender] a').values().map(a=>a.href)}]});document.head.appendChild(s);}
Performance
#517 JS Only

Live Region Timer

Announces a countdown to screen readers using an aria-live region.

Attribute
data-live-timer
JS JavaScript
document.querySelectorAll('[data-live-timer]').forEach(el=>{el.setAttribute('aria-live','polite');el.setAttribute('aria-atomic','true');let sec=parseInt(el.dataset.liveTimer||'60');const tick=()=>{el.textContent=`${sec} seconds remaining`;if(sec-->0)setTimeout(tick,1000);};tick();});
Accessibility
#518 CSS + JS

Canvas Signature Pad

A canvas-based signature drawing pad that saves to an image.

Attribute
data-sig-pad
CSS
[data-sig-pad]{border:1px solid rgba(255,255,255,0.15);border-radius:8px;cursor:crosshair;touch-action:none;}
JS JavaScript
document.querySelectorAll('[data-sig-pad]').forEach(canvas=>{const c=canvas as HTMLCanvasElement;const ctx=c.getContext('2d')!;let drawing=false;ctx.strokeStyle='#6366f1';ctx.lineWidth=2;ctx.lineCap='round';c.addEventListener('pointerdown',e=>{drawing=true;ctx.beginPath();ctx.moveTo(e.offsetX,e.offsetY);});c.addEventListener('pointermove',e=>{if(!drawing)return;ctx.lineTo(e.offsetX,e.offsetY);ctx.stroke();});c.addEventListener('pointerup',()=>drawing=false);});
Media
#519 JS Only

Voice Command

Listens for a voice command and triggers an action.

Attribute
data-voice-cmd
JS JavaScript
document.querySelectorAll('[data-voice-cmd]').forEach(el=>{const SpeechRecognition=(window as any).SpeechRecognition||(window as any).webkitSpeechRecognition;if(!SpeechRecognition)return;const rec=new SpeechRecognition();rec.onresult=e=>{const said=e.results[0][0].transcript.toLowerCase();if(said.includes(el.dataset.voiceCmd||''))el.dispatchEvent(new CustomEvent('voice-triggered'));};el.addEventListener('click',()=>rec.start());});
Interaction
#520 CSS + JS

Scroll to Reveal Secret

A hidden message reveals after the user scrolls past a specific element.

Attribute
data-scroll-secret
CSS
[data-secret-msg]{opacity:0;transform:scale(0.8);transition:opacity 0.6s,transform 0.6s;}[data-secret-msg].revealed{opacity:1;transform:none;}
JS JavaScript
const trigger=document.querySelector('[data-scroll-secret]');const msg=document.querySelector('[data-secret-msg]');if(trigger)new IntersectionObserver(([e])=>{if(e.isIntersecting)msg?.classList.add('revealed');}).observe(trigger);
Interaction
#521 JS Only

Adaptive Color Scheme

Reads system accent color and applies it as a CSS variable.

Attribute
data-sys-accent
JS JavaScript
const accent=getComputedStyle(document.documentElement).getPropertyValue('--system-accent')||'#6366f1';document.documentElement.style.setProperty('--accent',accent);
Color & Theme
#522 JS Only

Clipboard History

Tracks the last 5 clipboard entries and lets you re-paste any.

Attribute
data-clip-history
JS JavaScript
const hist:string[]=[];document.addEventListener('copy',()=>{const sel=window.getSelection()?.toString();if(sel){hist.unshift(sel);if(hist.length>5)hist.pop();document.querySelector('[data-clip-history]')?.dispatchEvent(new CustomEvent('clip-update',{detail:[...hist]}));}});
Misc
#523 CSS + JS

Tilt Background on Scroll

Page background gradient tilts its angle as you scroll.

Attribute
data-tilt-bg
CSS
body{background:linear-gradient(var(--tilt-deg,135deg),#6366f1,#ec4899);}
JS JavaScript
window.addEventListener('scroll',()=>{const deg=135+scrollY*0.05;document.documentElement.style.setProperty('--tilt-deg',deg+'deg');},{passive:true});
Misc
#524 CSS Only

Spin on Hover

Icon or element spins 360° on hover.

Attribute
data-spin-hover
CSS
[data-spin-hover]{transition:transform 0.5s ease;}[data-spin-hover]:hover{transform:rotate(360deg);}
Animations
#525 CSS Only

Flip Logo on Scroll

Logo flips on the Y axis as it enters the viewport.

Attribute
data-logo-flip
CSS
[data-logo-flip]{animation:logoFlip linear both;animation-timeline:view();animation-range:entry 0% entry 20%;}@keyframes logoFlip{from{transform:perspective(400px) rotateY(-90deg);opacity:0;}to{transform:none;opacity:1;}}
Animations
#526 CSS + JS

Parallax Text Layers

Multiple text layers scroll at different speeds for a parallax depth effect.

Attribute
data-para-text
CSS
[data-para-text] [data-para-layer]{will-change:transform;}
JS JavaScript
document.querySelectorAll('[data-para-text]').forEach(wrap=>{wrap.querySelectorAll<HTMLElement>('[data-para-layer]').forEach(layer=>{const speed=parseFloat(layer.dataset.paraLayer||'0.3');window.addEventListener('scroll',()=>layer.style.transform=`translateY(${scrollY*speed}px)`,{passive:true});});});
Scroll
#527 CSS + JS

Sortable Column Header

Arrow indicators toggle on table column headers to show sort direction.

Attribute
data-sort-col
CSS
[data-sort-col]{cursor:pointer;white-space:nowrap;}[data-sort-col]::after{content:' ⇅';opacity:0.3;}[data-sort-col].asc::after{content:' ↑';opacity:1;}[data-sort-col].desc::after{content:' ↓';opacity:1;}
JS JavaScript
document.querySelectorAll('[data-sort-col]').forEach(th=>{th.addEventListener('click',()=>{const asc=th.classList.toggle('asc');th.classList.toggle('desc',!asc);});});
UI Patterns
#528 JS Only

Smart Date Input

Reformats date input to locale string on blur.

Attribute
data-smart-date
JS JavaScript
document.querySelectorAll('[data-smart-date]').forEach(inp=>{inp.addEventListener('blur',()=>{const d=new Date((inp as HTMLInputElement).value);if(!isNaN(d.getTime()))(inp as HTMLInputElement).value=d.toLocaleDateString();});});
Forms
#529 CSS Only

Breakout Hero

Hero section overflows container to hit viewport edges.

Attribute
data-breakout
CSS
[data-breakout]{position:relative;left:50%;right:50%;margin-left:-50vw;margin-right:-50vw;width:100vw;}
Layout
#530 CSS + JS

Skeleton Replace on Load

Swaps skeleton placeholders with real content after data loads.

Attribute
data-skeleton-swap
CSS
[data-skeleton-swap]{min-height:2rem;border-radius:6px;background:linear-gradient(90deg,#333 25%,#444 50%,#333 75%);background-size:200%;animation:shimmer 1.5s infinite;}[data-skeleton-swap].loaded{background:none;animation:none;min-height:auto;}
JS JavaScript
document.querySelectorAll('[data-skeleton-swap]').forEach(el=>{const src=el.dataset.skeletonSwap;if(src)fetch(src).then(r=>r.text()).then(html=>{el.innerHTML=html;el.classList.add('loaded');});});
Performance
#531 JS Only

Motion Preference Query

Reads prefers-reduced-motion and sets a data attribute for conditional CSS.

Attribute
data-motion-pref
JS JavaScript
const reduced=window.matchMedia('(prefers-reduced-motion: reduce)').matches;document.documentElement.setAttribute('data-motion-pref',reduced?'reduce':'no-preference');
Accessibility
#532 JS Only

Scroll-Triggered Video Play

Video plays when scrolled into view and pauses when out.

Attribute
data-scroll-video
JS JavaScript
const obs=new IntersectionObserver(es=>es.forEach(e=>{const v=e.target as HTMLVideoElement;e.isIntersecting?v.play().catch(()=>{}):v.pause();}),{threshold:0.4});document.querySelectorAll('[data-scroll-video]').forEach(v=>{(v as HTMLVideoElement).muted=true;obs.observe(v);});
Media
#533 JS Only

Wheel Color Picker

Mouse wheel changes hue on a colored element.

Attribute
data-wheel-hue
JS JavaScript
document.querySelectorAll('[data-wheel-hue]').forEach(el=>{let hue=200;el.addEventListener('wheel',e=>{e.preventDefault();hue=(hue+Math.sign(e.deltaY)*5+360)%360;(el as HTMLElement).style.background=`hsl(${hue},80%,60%)`;},{passive:false});});
Interaction
#534 CSS Only

CSS Logical Properties

Uses inline-start/end instead of left/right for bidirectional layouts.

Attribute
data-logical
CSS
[data-logical]{padding-inline:1.5rem;padding-block:1rem;margin-inline:auto;border-inline-start:4px solid #6366f1;}
Color & Theme
#535 CSS + JS

Fake Loading Screen

Shows a branded loading overlay that hides once the page loads.

Attribute
data-loading-screen
CSS
[data-loading-screen]{position:fixed;inset:0;background:#0a0a0c;z-index:99999;display:flex;align-items:center;justify-content:center;transition:opacity 0.5s;}[data-loading-screen].done{opacity:0;pointer-events:none;}
JS JavaScript
window.addEventListener('load',()=>setTimeout(()=>document.querySelector('[data-loading-screen]')?.classList.add('done'),800));
Misc
#536 CSS Only

Sweep Reveal

Content is revealed by a colored sweep overlay moving left to right.

Attribute
data-sweep-reveal
CSS
[data-sweep-reveal]{position:relative;overflow:hidden;}[data-sweep-reveal]::before{content:'';position:absolute;inset:0;background:#6366f1;transform:translateX(0);animation:sweepReveal linear both;animation-timeline:view();animation-range:entry 0% entry 30%;}@keyframes sweepReveal{to{transform:translateX(101%);}}
Animations
#537 CSS + JS

Reading Mode Wide Toggle

Toggles a max-width constraint for comfortable article reading.

Attribute
data-reading-mode
CSS
[data-reading-mode].reading{max-width:68ch;margin-inline:auto;font-size:1.1rem;line-height:1.8;}
JS JavaScript
document.querySelector('[data-reading-toggle]')?.addEventListener('click',()=>document.querySelector('[data-reading-mode]')?.classList.toggle('reading'));
Scroll
#538 CSS + JS

Keyboard Shortcut List Overlay

Shows a reference overlay of all keyboard shortcuts on ?.

Attribute
data-shortcut-overlay
CSS
[data-shortcut-overlay]{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.8);z-index:9999;align-items:center;justify-content:center;}[data-shortcut-overlay].show{display:flex;}
JS JavaScript
const ov=document.querySelector('[data-shortcut-overlay]');document.addEventListener('keydown',e=>{if(e.key==='?')ov?.classList.toggle('show');if(e.key==='Escape')ov?.classList.remove('show');});
UI Patterns
#539 JS Only

Parallax Star Field

Tiny star dots scroll at varying speeds to create a deep-space effect.

Attribute
data-starfield
CSS
[data-starfield]{position:relative;overflow:hidden;background:#000;}
JS JavaScript
document.querySelectorAll('[data-starfield]').forEach(el=>{for(let i=0;i<120;i++){const s=document.createElement('span');const size=Math.random()*2+1;Object.assign(s.style,{position:'absolute',width:size+'px',height:size+'px',borderRadius:'50%',background:'#fff',opacity:String(Math.random()*0.8+0.2),top:Math.random()*100+'%',left:Math.random()*100+'%',animation:`starDrift ${Math.random()*30+20}s linear ${Math.random()*10}s infinite`});el.appendChild(s);}const style=document.createElement('style');style.textContent='@keyframes starDrift{to{transform:translateY(10%);}}';document.head.appendChild(style);});
Visual FX
#540 JS Only

Hotkey Panel Toggle

Toggles any panel element with a configurable keyboard shortcut.

Attribute
data-hotkey="alt+p"
JS JavaScript
document.querySelectorAll('[data-hotkey]').forEach(el=>{const parts=(el.dataset.hotkey||'').toLowerCase().split('+');const key=parts.pop()||'';const mods={alt:parts.includes('alt'),ctrl:parts.includes('ctrl'),shift:parts.includes('shift'),meta:parts.includes('meta')};document.addEventListener('keydown',e=>{if(e.altKey===mods.alt&&e.ctrlKey===mods.ctrl&&e.shiftKey===mods.shift&&e.metaKey===mods.meta&&e.key.toLowerCase()===key){e.preventDefault();el.dispatchEvent(new CustomEvent('hotkey-triggered'));}});});
Misc
#541 CSS Only

Glow Pulse Icon

An icon glows outward with alternating intensity.

Attribute
data-glow-icon
CSS
[data-glow-icon]{filter:drop-shadow(0 0 4px currentColor);animation:glowPulse 2s ease-in-out infinite;}@keyframes glowPulse{50%{filter:drop-shadow(0 0 12px currentColor);}}
Animations
#542 JS Only

Mobile-Only Parallax

Applies tilt/parallax only on mobile via deviceorientation.

Attribute
data-gyro-parallax
JS JavaScript
if(window.DeviceOrientationEvent){window.addEventListener('deviceorientation',e=>{const x=(e.gamma||0)/90*10;const y=(e.beta||0)/90*10;document.querySelectorAll('[data-gyro-parallax]').forEach(el=>(el as HTMLElement).style.transform=`translate(${x}px,${y}px)`);});}
Scroll
#543 JS Only

Scroll Direction Lock

Locks scroll direction once movement starts, preventing diagonal scroll.

Attribute
data-scroll-lock-dir
JS JavaScript
document.querySelectorAll('[data-scroll-lock-dir]').forEach(el=>{let dir:string|null=null;el.addEventListener('touchstart',()=>dir=null,{passive:true});el.addEventListener('touchmove',e=>{if(!dir){const t=e.touches[0];const dx=Math.abs(t.clientX);const dy=Math.abs(t.clientY);dir=dx>dy?'x':'y';}if(dir==='x')e.preventDefault();},{passive:false});});
UI Patterns
#544 CSS Only

Noise Animated Background

SVG noise pattern shifts and moves slowly for organic texture.

Attribute
data-noise-anim
CSS
[data-noise-anim]::after{content:'';position:absolute;inset:-50%;width:200%;height:200%;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence baseFrequency='0.65' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");animation:noiseShift 8s steps(1) infinite;pointer-events:none;}@keyframes noiseShift{to{transform:translate(5%,5%);}}
Visual FX
#545 CSS + JS

Formatted JSON Editor

Formats and validates JSON in a textarea on blur.

Attribute
data-json-editor
CSS
[data-json-editor].error{border-color:#ef4444;}[data-json-editor].valid{border-color:#22c55e;}
JS JavaScript
document.querySelectorAll('[data-json-editor]').forEach(el=>{el.addEventListener('blur',()=>{try{const obj=JSON.parse((el as HTMLTextAreaElement).value);(el as HTMLTextAreaElement).value=JSON.stringify(obj,null,2);el.classList.add('valid');el.classList.remove('error');}catch{el.classList.add('error');el.classList.remove('valid');}});});
Forms
#546 JS Only

Render Blocking Detector

Warns in console if any synchronous scripts delay page render.

Attribute
data-render-check
JS JavaScript
const blocking=document.querySelectorAll('script:not([async]):not([defer]):not([type="module"])');if(blocking.length)console.warn(`[Render Blocking] ${blocking.length} blocking scripts found`,blocking);
Performance
#547 JS Only

Landmark Role Validator

Warns in console if essential ARIA landmarks are missing from the page.

Attribute
data-landmark-check
JS JavaScript
['main','nav','footer'].forEach(role=>{if(!document.querySelector(`[${role}],[`+`role="${role}"]`))console.warn(`[A11y] Missing landmark: <${role}>`);});
Accessibility
#548 CSS + JS

Drag Count Tracker

Tracks how many items have been dragged into a drop zone.

Attribute
data-drag-counter
CSS
[data-drag-counter-display]::before{content:attr(data-count)' items';}
JS JavaScript
document.querySelectorAll('[data-drag-counter]').forEach(zone=>{let count=0;const disp=zone.querySelector('[data-drag-counter-display]');zone.addEventListener('drop',()=>{disp?.setAttribute('data-count',String(++count));});zone.addEventListener('dragover',e=>e.preventDefault());});
Interaction
#549 CSS + JS

Ambient Room Color

Blends the dominant image color as an ambient glow behind the image.

Attribute
data-ambient
CSS
[data-ambient]{position:relative;}[data-ambient]::after{content:'';position:absolute;inset:-20px;z-index:-1;border-radius:50%;filter:blur(40px);background:var(--ambient-color,#6366f1);opacity:0.5;}
JS JavaScript
document.querySelectorAll('[data-ambient]').forEach(img=>{const canvas=document.createElement('canvas');canvas.width=canvas.height=1;canvas.getContext('2d')?.drawImage(img as HTMLImageElement,0,0,1,1);const[r,g,b]=canvas.getContext('2d')?.getImageData(0,0,1,1).data||[99,102,241,255];(img as HTMLElement).style.setProperty('--ambient-color',`rgb(${r},${g},${b})`);});
Color & Theme
#550 JS Only

Section Time Tracker

Logs how many seconds a user spends with each section visible.

Attribute
data-time-track
JS JavaScript
const times:Record<string,number>={};document.querySelectorAll('[data-time-track]').forEach(el=>{let start=0;const obs=new IntersectionObserver(([e])=>{if(e.isIntersecting)start=Date.now();else if(start){const id=el.id||'section';times[id]=(times[id]||0)+((Date.now()-start)/1000);start=0;}});obs.observe(el);window.addEventListener('visibilitychange',()=>{if(document.hidden&&start){const id=el.id||'section';times[id]=(times[id]||0)+((Date.now()-start)/1000);start=0;}});});window.addEventListener('beforeunload',()=>console.table(times));
Misc

How to use in Webflow

  1. Copy the attribute — click the code chip on any card to copy it.
  2. Add to your element — select the element in Webflow, open the Element Settings panel (D key), scroll to Custom Attributes and add it.
  3. Add the CSS — paste it into a Page Settings → Custom Code → <head> block, wrapped in <style> tags.
  4. Add the JS — paste it into Page Settings → Custom Code → </body> block, wrapped in <script> tags.
  5. Publish and test — custom attributes only work in the live published site, not the designer preview.
Start a project