Lagging Ring Cursor
An outlined ring that eases in behind the pointer with a per-frame lerp.
3 frameworksIntermediate
A small dot replaces the native cursor and tracks the pointer inside its panel.
<!--
The dot replaces the native cursor, so cursor:none is applied by the script,
never by a class - if the script does not run (touch, JS off) there must
still be a visible cursor. All movement is one transform write per event.
-->
<div id="cursor-dot-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">Move your pointer around this panel</p>
</div>
<div id="cursor-dot" 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-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-blue-600 dark:bg-blue-400"></span>
</div>
</div>
<script>
(() => {
const stage = document.getElementById('cursor-dot-stage');
const dot = document.getElementById('cursor-dot');
if (!stage || !dot || !matchMedia('(pointer: fine)').matches) return;
let rect = stage.getBoundingClientRect();
stage.style.cursor = 'none';
stage.addEventListener('pointerenter', () => { rect = stage.getBoundingClientRect(); dot.style.opacity = '1'; });
stage.addEventListener('pointerleave', () => { dot.style.opacity = '0'; });
stage.addEventListener('pointermove', (e) => {
dot.style.transform = 'translate3d(' + (e.clientX - rect.left) + 'px,' + (e.clientY - rect.top) + 'px,0)';
});
})();
</script>| Prop | Type | Default | Description |
|---|---|---|---|
hint | string | 'Move your pointer around this panel' | Hint |
dotClassName | string | 'h-3 w-3 bg-blue-600 dark:bg-blue-400' | Dot class name |
className | string | - | Additional classes merged onto the root element. |
The dot colour and size live in `dotClassName`; swap in any Tailwind size/background. `cursor: none` is set by the script, never a class, so a non-fine pointer or a JS-off page keeps a real cursor.