Dot Cursor Follower
A small dot replaces the native cursor and tracks the pointer inside its panel.
3 frameworksBeginner
An outlined ring that eases in behind the pointer with a per-frame lerp.
<!--
The ring trails the pointer via a per-frame lerp in requestAnimationFrame. The
easing IS the effect, so with prefers-reduced-motion the factor becomes 1 and
the ring tracks the pointer 1:1 instead of trailing it.
-->
<div id="cursor-ring-stage" class="relative w-full max-w-lg overflow-hidden rounded-2xl border border-gray-200 bg-gray-50 dark:border-gray-800 dark:bg-gray-900">
<div class="flex h-56 items-center justify-center px-6 text-center">
<p class="text-sm text-gray-600 dark:text-gray-400">The ring eases in behind your pointer</p>
</div>
<div id="cursor-ring" class="pointer-events-none absolute left-0 top-0 opacity-0 transition-opacity duration-150 motion-reduce:transition-none" aria-hidden="true">
<span class="block h-8 w-8 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-blue-500 dark:border-blue-400"></span>
</div>
</div>
<script>
(() => {
const stage = document.getElementById('cursor-ring-stage');
const ring = document.getElementById('cursor-ring');
if (!stage || !ring || !matchMedia('(pointer: fine)').matches) return;
const factor = matchMedia('(prefers-reduced-motion: reduce)').matches ? 1 : 0.15;
let rect = stage.getBoundingClientRect();
let tx = 0, ty = 0, cx = 0, cy = 0, raf = 0, active = false;
const loop = () => {
cx += (tx - cx) * factor; cy += (ty - cy) * factor;
ring.style.transform = 'translate3d(' + cx + 'px,' + cy + 'px,0)';
raf = requestAnimationFrame(loop);
};
stage.style.cursor = 'none';
stage.addEventListener('pointerenter', (e) => {
rect = stage.getBoundingClientRect();
tx = cx = e.clientX - rect.left; ty = cy = e.clientY - rect.top;
if (!active) { active = true; ring.style.opacity = '1'; raf = requestAnimationFrame(loop); }
});
stage.addEventListener('pointerleave', () => { active = false; ring.style.opacity = '0'; cancelAnimationFrame(raf); });
stage.addEventListener('pointermove', (e) => { tx = e.clientX - rect.left; ty = e.clientY - rect.top; });
})();
</script>| Prop | Type | Default | Description |
|---|---|---|---|
hint | string | 'The ring eases in behind your pointer' | Hint |
ringClassName | string | 'h-8 w-8 border-2 border-blue-500 dark:border-blue-400' | Ring class name |
ease | number | 0.15 | Ease |
className | string | - | Additional classes merged onto the root element. |
Tune the trail with `ease` (lower lags further) and restyle the ring via `ringClassName`. The lag is the effect, so `prefers-reduced-motion` drops the factor to 1 and the ring tracks the pointer exactly.