30 CSS Hover Effects29 / 30

CSS + JSMIT licensed

CSS Dot Trail Cursor Hover Effect

A trail of fading dot particles follows the cursor inside a container — four trail styles including color rainbow, size decay, and comet tail.

Published

Live Demo
Try it

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

The code

<div class="hv-29">
  <p class="hv-29__label">Dot Trail Cursor — 4 Styles</p>
  <div class="hv-29__grid">

    <div class="hv-29__zone hv-29__zone--classic" data-trail="classic">
      <span class="hv-29__hint">Classic Fade</span>
    </div>

    <div class="hv-29__zone hv-29__zone--rainbow" data-trail="rainbow">
      <span class="hv-29__hint">Rainbow</span>
    </div>

    <div class="hv-29__zone hv-29__zone--comet" data-trail="comet">
      <span class="hv-29__hint">Comet Tail</span>
    </div>

    <div class="hv-29__zone hv-29__zone--sparkle" data-trail="sparkle">
      <span class="hv-29__hint">Sparkle</span>
    </div>

  </div>
</div>
.hv-29,
.hv-29 *,
.hv-29 *::before,
.hv-29 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.hv-29 {
  font-family: system-ui, sans-serif;
  background: #080810;
  padding: 2rem;
  min-height: 100vh;
  user-select: none;
}

.hv-29__label {
  text-align: center;
  color: #444;
  font-size: .72rem;
  letter-spacing: .15em;
  text-transform: uppercase;
  margin-bottom: 1.5rem;
}

.hv-29__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  max-width: 700px;
  margin: 0 auto;
}

.hv-29__zone {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  aspect-ratio: 4/3;
  border: 1px solid #1a1a2a;
  cursor: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hv-29__zone--classic {
  background: #0d0d18;
}

.hv-29__zone--rainbow {
  background: #0d0d18;
}

.hv-29__zone--comet {
  background: #0a0a14;
}

.hv-29__zone--sparkle {
  background: #0c0c18;
}

.hv-29__hint {
  color: #2a2a40;
  font-size: .75rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  pointer-events: none;
}
/* ── Dot base ── */

.hv-29__dot {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
}
/* ── Classic: white dots fade & shrink ── */

@keyframes hv-29-classic {
  0% {
    opacity: .9;
    transform: translate(-50%,-50%) scale(1);
  }

  100% {
    opacity: 0;
    transform: translate(-50%,-50%) scale(0);
  }
}

.hv-29__zone--classic .hv-29__dot {
  width: 8px;
  height: 8px;
  background: #fff;
  animation: hv-29-classic .6s ease-out forwards;
}
/* ── Rainbow: hue-rotated dots ── */

@keyframes hv-29-rainbow {
  0% {
    opacity: 1;
    transform: translate(-50%,-50%) scale(1.1);
  }

  100% {
    opacity: 0;
    transform: translate(-50%,-50%) scale(0.1);
  }
}

.hv-29__zone--rainbow .hv-29__dot {
  width: 10px;
  height: 10px;
  animation: hv-29-rainbow .7s ease-out forwards;
  /* background set inline via JS */
}
/* ── Comet: elongated fade to right ── */

@keyframes hv-29-comet {
  0% {
    opacity: 1;
    transform: translate(-50%,-50%) scale(1, 1);
  }

  100% {
    opacity: 0;
    transform: translate(-50%,-50%) scale(3, .3);
  }
}

.hv-29__zone--comet .hv-29__dot {
  width: 12px;
  height: 4px;
  background: linear-gradient(90deg, rgba(100,180,255,0), rgba(100,180,255,1));
  border-radius: 2px;
  animation: hv-29-comet .5s ease-out forwards;
}
/* ── Sparkle: star-like burst ── */

@keyframes hv-29-sparkle {
  0% {
    opacity: 1;
    transform: translate(-50%,-50%) scale(0) rotate(0deg);
  }

  60% {
    opacity: 1;
    transform: translate(-50%,-50%) scale(1.3) rotate(60deg);
  }

  100% {
    opacity: 0;
    transform: translate(-50%,-50%) scale(0.4) rotate(90deg);
  }
}

.hv-29__zone--sparkle .hv-29__dot {
  width: 10px;
  height: 10px;
  clip-path: polygon(50% 0%,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%);
  background: #f7d060;
  animation: hv-29-sparkle .65s ease-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  .hv-29__dot {
    animation-duration: .01ms !important;
  }
}
(function () {
  const zones = document.querySelectorAll('.hv-29__zone');
  const RAINBOW = ['#ff6b6b','#ffa94d','#ffd43b','#69db7c','#4dabf7','#cc5de8','#f783ac'];
  let hueIndex = 0;
  let rafId = null;
  let pendingZone = null;
  let pendingX = 0;
  let pendingY = 0;

  function spawnDot(zone, x, y) {
    const trail = zone.dataset.trail;
    const dot = document.createElement('span');
    dot.className = 'hv-29__dot';
    dot.style.left = x + 'px';
    dot.style.top  = y + 'px';
    if (trail === 'rainbow') {
      dot.style.background = RAINBOW[hueIndex % RAINBOW.length];
      hueIndex++;
    }
    zone.appendChild(dot);
    dot.addEventListener('animationend', () => dot.remove(), { once: true });
  }

  function onMove(e) {
    const zone = e.currentTarget;
    const rect = zone.getBoundingClientRect();
    pendingX = e.clientX - rect.left;
    pendingY = e.clientY - rect.top;
    pendingZone = zone;
    if (!rafId) {
      rafId = requestAnimationFrame(() => {
        if (pendingZone) spawnDot(pendingZone, pendingX, pendingY);
        rafId = null;
      });
    }
  }

  zones.forEach(zone => {
    zone.addEventListener('mousemove', onMove);
    zone.addEventListener('mouseleave', () => { pendingZone = null; });
  });
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Hover Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Dot Trail Cursor Hover Effect
Source: https://codefronts.com/motion/css-hover-effects/css-dot-trail-cursor-hover-effect/

A trail of fading dot particles follows the cursor inside a container — four trail styles including color rainbow, size decay, and comet tail.
## HTML
```html
<div class="hv-29">
  <p class="hv-29__label">Dot Trail Cursor — 4 Styles</p>
  <div class="hv-29__grid">

    <div class="hv-29__zone hv-29__zone--classic" data-trail="classic">
      <span class="hv-29__hint">Classic Fade</span>
    </div>

    <div class="hv-29__zone hv-29__zone--rainbow" data-trail="rainbow">
      <span class="hv-29__hint">Rainbow</span>
    </div>

    <div class="hv-29__zone hv-29__zone--comet" data-trail="comet">
      <span class="hv-29__hint">Comet Tail</span>
    </div>

    <div class="hv-29__zone hv-29__zone--sparkle" data-trail="sparkle">
      <span class="hv-29__hint">Sparkle</span>
    </div>

  </div>
</div>
```
## CSS
```css
.hv-29,
.hv-29 *,
.hv-29 *::before,
.hv-29 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.hv-29 {
  font-family: system-ui, sans-serif;
  background: #080810;
  padding: 2rem;
  min-height: 100vh;
  user-select: none;
}

.hv-29__label {
  text-align: center;
  color: #444;
  font-size: .72rem;
  letter-spacing: .15em;
  text-transform: uppercase;
  margin-bottom: 1.5rem;
}

.hv-29__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  max-width: 700px;
  margin: 0 auto;
}

.hv-29__zone {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  aspect-ratio: 4/3;
  border: 1px solid #1a1a2a;
  cursor: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hv-29__zone--classic {
  background: #0d0d18;
}

.hv-29__zone--rainbow {
  background: #0d0d18;
}

.hv-29__zone--comet {
  background: #0a0a14;
}

.hv-29__zone--sparkle {
  background: #0c0c18;
}

.hv-29__hint {
  color: #2a2a40;
  font-size: .75rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  pointer-events: none;
}
/* ── Dot base ── */

.hv-29__dot {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
}
/* ── Classic: white dots fade & shrink ── */

@keyframes hv-29-classic {
  0% {
    opacity: .9;
    transform: translate(-50%,-50%) scale(1);
  }

  100% {
    opacity: 0;
    transform: translate(-50%,-50%) scale(0);
  }
}

.hv-29__zone--classic .hv-29__dot {
  width: 8px;
  height: 8px;
  background: #fff;
  animation: hv-29-classic .6s ease-out forwards;
}
/* ── Rainbow: hue-rotated dots ── */

@keyframes hv-29-rainbow {
  0% {
    opacity: 1;
    transform: translate(-50%,-50%) scale(1.1);
  }

  100% {
    opacity: 0;
    transform: translate(-50%,-50%) scale(0.1);
  }
}

.hv-29__zone--rainbow .hv-29__dot {
  width: 10px;
  height: 10px;
  animation: hv-29-rainbow .7s ease-out forwards;
  /* background set inline via JS */
}
/* ── Comet: elongated fade to right ── */

@keyframes hv-29-comet {
  0% {
    opacity: 1;
    transform: translate(-50%,-50%) scale(1, 1);
  }

  100% {
    opacity: 0;
    transform: translate(-50%,-50%) scale(3, .3);
  }
}

.hv-29__zone--comet .hv-29__dot {
  width: 12px;
  height: 4px;
  background: linear-gradient(90deg, rgba(100,180,255,0), rgba(100,180,255,1));
  border-radius: 2px;
  animation: hv-29-comet .5s ease-out forwards;
}
/* ── Sparkle: star-like burst ── */

@keyframes hv-29-sparkle {
  0% {
    opacity: 1;
    transform: translate(-50%,-50%) scale(0) rotate(0deg);
  }

  60% {
    opacity: 1;
    transform: translate(-50%,-50%) scale(1.3) rotate(60deg);
  }

  100% {
    opacity: 0;
    transform: translate(-50%,-50%) scale(0.4) rotate(90deg);
  }
}

.hv-29__zone--sparkle .hv-29__dot {
  width: 10px;
  height: 10px;
  clip-path: polygon(50% 0%,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%);
  background: #f7d060;
  animation: hv-29-sparkle .65s ease-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  .hv-29__dot {
    animation-duration: .01ms !important;
  }
}
```

## JavaScript
```js
(function () {
  const zones = document.querySelectorAll('.hv-29__zone');
  const RAINBOW = ['#ff6b6b','#ffa94d','#ffd43b','#69db7c','#4dabf7','#cc5de8','#f783ac'];
  let hueIndex = 0;
  let rafId = null;
  let pendingZone = null;
  let pendingX = 0;
  let pendingY = 0;

  function spawnDot(zone, x, y) {
    const trail = zone.dataset.trail;
    const dot = document.createElement('span');
    dot.className = 'hv-29__dot';
    dot.style.left = x + 'px';
    dot.style.top  = y + 'px';
    if (trail === 'rainbow') {
      dot.style.background = RAINBOW[hueIndex % RAINBOW.length];
      hueIndex++;
    }
    zone.appendChild(dot);
    dot.addEventListener('animationend', () => dot.remove(), { once: true });
  }

  function onMove(e) {
    const zone = e.currentTarget;
    const rect = zone.getBoundingClientRect();
    pendingX = e.clientX - rect.left;
    pendingY = e.clientY - rect.top;
    pendingZone = zone;
    if (!rafId) {
      rafId = requestAnimationFrame(() => {
        if (pendingZone) spawnDot(pendingZone, pendingX, pendingY);
        rafId = null;
      });
    }
  }

  zones.forEach(zone => {
    zone.addEventListener('mousemove', onMove);
    zone.addEventListener('mouseleave', () => { pendingZone = null; });
  });
})();
```

How this works

JavaScript tracks mousemove events and spawns absolutely-positioned elements at cursor coordinates. Each dot gets a CSS class (or inline custom property) and uses a CSS animation to fade out and shrink. Spawned dots are removed from the DOM after their animation ends via animationend. Four CSS @keyframes variants handle different visual effects.

Make it yours

  • Change --dot-color for a solid color trail, --dot-size for dot radius, throttle spawn rate via the requestAnimationFrame interval, or swap in SVG icons instead of dots.

Gotchas — read before shipping

  • Always use position:absolute dots inside a position:relative container — not the document body — to avoid scroll offset bugs.
  • Remove dots on animationend, not with setTimeout, so duration changes in CSS automatically clean up correctly.
  • Throttle spawning to every ~16ms (rAF) max; unthrottled mousemove can create thousands of elements per second.

Browser support

ChromeSafariFirefoxEdge

animationend event universally supported. CSS custom properties on elements require modern browsers.

Techniques used in this demo

Search CodeFronts

Loading…