3 Pure CSS4 with JSPublished 2026-10-05Free · no sign-up
Seven cursor ideas, from the useful to the playful: the built-in cursor keywords, a custom SVG cursor with a hotspot, a soft circle that follows the pointer, a ring that grows over links, a spotlight that reveals a picture, an emoji cursor on one button and a short dot trail.
A grid of tiles, each showing one built-in cursor value when hovered: pointer, grab, zoom-in, not-allowed, text, help, move and progress. Using the right built-in cursor communicates what will happen without any custom images.
cursor: url(data:image/svg+xml,…) 16 16, crosshair sets an inline SVG as the cursor, with the hotspot at its centre and a built-in fallback after the comma. Keep custom cursors small (32px or less) and only inside the area where they mean something, such as a canvas.
View code
<div class="cur2" role="img" aria-label="Drawing area with a crosshair cursor">Move the pointer here</div>
A translucent circle trails the real cursor with a little lag, a playful touch for creative portfolios. The real cursor is never hidden, the circle ignores pointer events, and on touch screens or with reduced motion it does not appear at all.
View code
<div class="cur3"><span class="dot" aria-hidden="true"></span><p>Move the pointer around this area.</p></div>
var box = document.querySelector('.cur3'), dot = box.querySelector('.dot'), x = 0, y = 0, cx = 0, cy = 0;
box.addEventListener('pointermove', function (e) { var r = box.getBoundingClientRect(); x = e.clientX - r.left; y = e.clientY - r.top; });
(function loop() { cx += (x - cx) * .18; cy += (y - cy) * .18; dot.style.transform = 'translate(' + cx + 'px,' + cy + 'px)'; requestAnimationFrame(loop); })();
04. Follower That Grows over Links
CursorHover
The cursor ring grows and fills when it passes over a link, signalling something clickable. The ring checks the element under the pointer with closest('a, button'), and links keep their own hover and focus styles for anyone not using a mouse.
var box = document.querySelector('.cur4'), ring = box.querySelector('.ring');
box.addEventListener('pointermove', function (e) { var r = box.getBoundingClientRect(); ring.style.transform = 'translate(' + (e.clientX - r.left) + 'px,' + (e.clientY - r.top) + 'px)'; ring.classList.toggle('big', !!e.target.closest('a, button')); });
05. Spotlight Reveal Under the Cursor
CursorMask
A dark overlay with a clear circle wherever the pointer is, uncovering the picture beneath like a torch. The overlay is a radial-gradient mask whose centre follows the pointer, and keyboard users get the full picture because the overlay hides when the area has focus.
View code
<div class="cur5" tabindex="0" role="img" aria-label="Hidden mural of a city at night"><div class="pic"></div><div class="dark" aria-hidden="true"></div></div>
var b = document.querySelector('.cur5');
b.addEventListener('pointermove', function (e) { var r = b.getBoundingClientRect(); b.style.setProperty('--x', e.clientX - r.left + 'px'); b.style.setProperty('--y', e.clientY - r.top + 'px'); });
06. Emoji Cursor on a Button
CursorFun
A playful cursor for one special element, here an SVG containing an emoji as text, with pointer as the fallback. Custom cursors work best on a single, clearly bounded element; across a whole page they slow people down.
A few dots follow the pointer, each easing towards the one ahead, so they string out into a short comet tail. It is purely decorative, capped at six dots, never hides the real cursor and switches off for touch input and reduced motion.
View code
<div class="cur7"><p>Draw a loop with the pointer.</p></div>
var box = document.querySelector('.cur7'), dots = [], x = -50, y = -50;
for (var i = 0; i < 6; i++) { var d = document.createElement('i'); d.setAttribute('aria-hidden', 'true'); d.style.opacity = 1 - i / 6; d.style.scale = 1 - i / 8; box.appendChild(d); dots.push({ el: d, x: -50, y: -50 }); }
box.addEventListener('pointermove', function (e) { var r = box.getBoundingClientRect(); x = e.clientX - r.left; y = e.clientY - r.top; });
(function loop() { var px = x, py = y; dots.forEach(function (d) { d.x += (px - d.x) * .35; d.y += (py - d.y) * .35; d.el.style.transform = 'translate(' + d.x + 'px,' + d.y + 'px)'; px = d.x; py = d.y; }); requestAnimationFrame(loop); })();
About these CSS Custom Cursor Styles
Built-in cursors first
Values like pointer, grab, zoom-in and not-allowed tell people what will happen and need no images. Use the right one before reaching for a custom design.
Custom cursors need a fallback
cursor: url(image) 16 16, pointer sets the image, its hotspot and a built-in fallback. Keep the image 32 pixels or smaller, or some browsers ignore it.
Never hide the real cursor
Follower and trail effects should sit on top of the normal cursor, not replace it. Hiding the system cursor slows people down and breaks magnifiers and accessibility tools.
Switch off where it makes no sense
Touch screens have no cursor, so disable followers under @media (pointer: coarse), and stop them for prefers-reduced-motion.
Frequently asked questions
Do custom cursors work on phones?
No. Phones have no cursor, so the effects are switched off on touch screens.
Are cursor followers bad for performance?
One element moved with transform in requestAnimationFrame is cheap. Avoid dozens of trailing elements.
Can I use an emoji as a cursor?
Yes, inside an SVG data URL as text, with a normal cursor as the fallback.