SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/custom-cursors

7 CSS Custom Cursor Styles

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.

01. Built-in Cursor Keywords

CursorReference

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.

View code
<ul class="cur1"><li style="cursor:pointer">pointer</li><li style="cursor:grab">grab</li><li style="cursor:zoom-in">zoom-in</li><li style="cursor:not-allowed">not-allowed</li><li style="cursor:text">text</li><li style="cursor:help">help</li><li style="cursor:move">move</li><li style="cursor:progress">progress</li></ul>
.cur1 { display: grid; grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: 6px; width: 400px; max-width: 100%; margin: 0; padding: 0; list-style: none; font: 600 12px "JetBrains Mono", ui-monospace, monospace; }
.cur1 li { display: grid; place-items: center; height: 54px; border: 1px dashed #d0d5dd; border-radius: 10px; background: #fff; color: #344054; }
.cur1 li:hover { border-style: solid; border-color: #7f56d9; background: #f9f5ff; }

02. Custom SVG Cursor with Hotspot

CursorSVG

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>
.cur2 { display: grid; place-items: center; width: 320px; max-width: 100%; height: 180px; border-radius: 14px; background: #0f172a repeating-linear-gradient(0deg, transparent 0 19px, #1e293b 19px 20px), repeating-linear-gradient(90deg, transparent 0 19px, #1e293b 19px 20px); color: #64748b; font: 600 13px Inter, system-ui, sans-serif; cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='7' fill='none' stroke='%2338bdf8' stroke-width='2'/%3E%3Cpath d='M16 2v8M16 22v8M2 16h8M22 16h8' stroke='%23f8fafc' stroke-width='2'/%3E%3C/svg%3E") 16 16, crosshair; }

03. Soft Circle That Follows the Cursor

CursorJavaScript

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>
.cur3 { position: relative; display: grid; place-items: center; width: 320px; max-width: 100%; height: 200px; overflow: hidden; border-radius: 14px; background: #fdf2fa; color: #9e165f; font: 600 14px Inter, system-ui, sans-serif; }
.cur3 p { margin: 0; }
.cur3 .dot { position: absolute; left: 0; top: 0; width: 36px; height: 36px; margin: -18px 0 0 -18px; border-radius: 50%; background: rgba(219,39,119,.35); pointer-events: none; opacity: 0; transition: opacity .2s; }
.cur3:hover .dot { opacity: 1; }
@media (pointer: coarse), (prefers-reduced-motion: reduce) { .cur3 .dot { display: none; } }
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.

View code
<div class="cur4"><span class="ring" aria-hidden="true"></span><nav aria-label="Demo"><a href="#work">Work</a><a href="#play">Play</a><a href="#talk">Talk</a></nav></div>
.cur4 { position: relative; display: grid; place-items: center; width: 320px; max-width: 100%; height: 200px; overflow: hidden; border-radius: 14px; background: #0f172a; font: 800 22px Inter, system-ui, sans-serif; }
.cur4 nav { display: flex; gap: 22px; }
.cur4 a { color: #f8fafc; text-decoration: none; } .cur4 a:hover, .cur4 a:focus-visible { color: #fbbf24; }
.cur4 a:focus-visible { outline: 2px solid #fbbf24; outline-offset: 4px; }
.cur4 .ring { position: absolute; left: 0; top: 0; width: 28px; height: 28px; margin: -14px 0 0 -14px; border: 2px solid #fbbf24; border-radius: 50%; pointer-events: none; opacity: 0; transition: width .2s, height .2s, margin .2s, background .2s, opacity .2s; }
.cur4:hover .ring { opacity: 1; }
.cur4 .ring.big { width: 56px; height: 56px; margin: -28px 0 0 -28px; background: rgba(251,191,36,.2); }
@media (pointer: coarse), (prefers-reduced-motion: reduce) { .cur4 .ring { display: none; } }
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>
.cur5 { --x: 50%; --y: 50%; position: relative; width: 320px; max-width: 100%; height: 200px; overflow: hidden; border-radius: 14px; }
.cur5 .pic { position: absolute; inset: 0; background: linear-gradient(transparent 60%, #0f172a 60%), repeating-linear-gradient(90deg, #f59e0b 0 18px, #1e3a8a 18px 26px, #db2777 26px 44px, #1e3a8a 44px 52px), linear-gradient(#312e81, #0f172a); }
.cur5 .dark { position: absolute; inset: 0; background: #020617; -webkit-mask: radial-gradient(70px circle at var(--x) var(--y), transparent 60%, #000 100%); mask: radial-gradient(70px circle at var(--x) var(--y), transparent 60%, #000 100%); transition: opacity .3s; }
.cur5:focus-visible { outline: 3px solid #fbbf24; outline-offset: 3px; }
.cur5:focus-visible .dark { opacity: 0; }
@media (pointer: coarse) { .cur5 .dark { opacity: .5; } }
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.

View code
<button class="cur6">Feed the cat</button>
.cur6 { padding: 16px 26px; border: 0; border-radius: 14px; background: #fde68a; color: #451a03; font: 800 16px Inter, system-ui, sans-serif; cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Ctext y='26' font-size='26'%3E%F0%9F%90%9F%3C/text%3E%3C/svg%3E") 16 16, pointer; }
.cur6:focus-visible { outline: 3px solid #b45309; outline-offset: 3px; }

07. Short Dot Trail

CursorTrail

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>
.cur7 { position: relative; display: grid; place-items: center; width: 320px; max-width: 100%; height: 200px; overflow: hidden; border-radius: 14px; background: #111827; color: #6b7280; font: 600 14px Inter, system-ui, sans-serif; }
.cur7 p { margin: 0; }
.cur7 i { position: absolute; left: 0; top: 0; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; background: #22d3ee; pointer-events: none; }
@media (pointer: coarse), (prefers-reduced-motion: reduce) { .cur7 i { display: none; } }
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.