Introduction
The custom cursor has become a signature element of modern web design. Sites like Apple, Stripe or Awwwards use it to create an immersive and memorable experience.
In this tutorial, we will explore 5 custom cursor variations, from simplest to most complex. Each demo is interactive - move your mouse in the designated areas to see the effect in action.
Custom cursors only work on desktop. On mobile, remember to provide a fallback or disable the effect.
1. Simple custom cursor (dot + circle)
The foundation of any custom cursor: a small central dot that follows the mouse exactly, surrounded by a larger circle with a slight delay (easing). It's elegant and unobtrusive.
Move your mouse in this area
// HTML: Add these elements to your body
// <div class="cursor-dot"></div>
// <div class="cursor-circle"></div>
const dot = document.querySelector('.cursor-dot');
const circle = document.querySelector('.cursor-circle');
let mouseX = 0, mouseY = 0;
let circleX = 0, circleY = 0;
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
// Dot follows instantly
dot.style.left = mouseX + 'px';
dot.style.top = mouseY + 'px';
});
// Circle animation with easing
function animate() {
const ease = 0.15;
circleX += (mouseX - circleX) * ease;
circleY += (mouseY - circleY) * ease;
circle.style.left = circleX + 'px';
circle.style.top = circleY + 'px';
requestAnimationFrame(animate);
}
animate();
/* Hide the native cursor */
body {
cursor: none;
}
.cursor-dot {
position: fixed;
width: 8px;
height: 8px;
background: #6366f1;
border-radius: 50%;
pointer-events: none;
z-index: 9999;
transform: translate(-50%, -50%);
}
.cursor-circle {
position: fixed;
width: 40px;
height: 40px;
border: 2px solid rgba(99, 102, 241, 0.5);
border-radius: 50%;
pointer-events: none;
z-index: 9998;
transform: translate(-50%, -50%);
transition: width 0.2s, height 0.2s, border-color 0.2s;
}
2. Trail effect
The trail effect creates a trail of particles that follow the cursor. Each particle fades out progressively, creating an elegant comet effect. Perfect for creative sites.
Move your mouse quickly to see the trail
const trailLength = 20;
const trails = [];
// Create trail elements
for (let i = 0; i < trailLength; i++) {
const trail = document.createElement('div');
trail.className = 'trail-particle';
trail.style.opacity = 1 - (i / trailLength);
trail.style.width = (12 - (i * 0.5)) + 'px';
trail.style.height = trail.style.width;
document.body.appendChild(trail);
trails.push({ el: trail, x: 0, y: 0 });
}
let mouseX = 0, mouseY = 0;
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});
function animateTrail() {
let x = mouseX, y = mouseY;
trails.forEach((trail, i) => {
const nextX = x;
const nextY = y;
trail.el.style.left = trail.x + 'px';
trail.el.style.top = trail.y + 'px';
x = trail.x;
y = trail.y;
trail.x += (nextX - trail.x) * 0.35;
trail.y += (nextY - trail.y) * 0.35;
});
requestAnimationFrame(animateTrail);
}
animateTrail();
.trail-particle {
position: fixed;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
border-radius: 50%;
pointer-events: none;
z-index: 9999;
transform: translate(-50%, -50%);
}
3. Magnetic cursor
The magnetic cursor is attracted to interactive elements (buttons, links). When the mouse approaches, the element seems "magnetized" toward the cursor. This is a technique widely used by creative agencies.
Bring your mouse close without clicking
const magneticElements = document.querySelectorAll('.magnetic');
const magnetStrength = 0.4; // Magnet strength
magneticElements.forEach(elem => {
elem.addEventListener('mousemove', (e) => {
const rect = elem.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const deltaX = (e.clientX - centerX) * magnetStrength;
const deltaY = (e.clientY - centerY) * magnetStrength;
elem.style.transform =
`translate(${deltaX}px, ${deltaY}px)`;
});
elem.addEventListener('mouseleave', () => {
elem.style.transform = 'translate(0, 0)';
});
});
// Version with expanded attraction zone
function initMagneticAdvanced(selector, options = {}) {
const { strength = 0.3, range = 100 } = options;
const elements = document.querySelectorAll(selector);
document.addEventListener('mousemove', (e) => {
elements.forEach(elem => {
const rect = elem.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const distance = Math.hypot(
e.clientX - centerX,
e.clientY - centerY
);
if (distance < range) {
const factor = 1 - (distance / range);
const deltaX = (e.clientX - centerX) * strength * factor;
const deltaY = (e.clientY - centerY) * strength * factor;
elem.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
} else {
elem.style.transform = 'translate(0, 0)';
}
});
});
}
4. Spotlight / Torch effect
The spotlight effect reveals content as if the user were holding a flashlight. Ideal for mysterious landing pages or product reveals.
Hidden content
Move the light to discover this secret text!
const spotlight = document.querySelector('.spotlight-overlay');
const container = document.querySelector('.spotlight-container');
container.addEventListener('mousemove', (e) => {
const rect = container.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
spotlight.style.background = `
radial-gradient(
circle 150px at ${x}px ${y}px,
transparent 0%,
rgba(10, 10, 15, 0.95) 100%
)
`;
});
container.addEventListener('mouseleave', () => {
spotlight.style.background = 'rgba(10, 10, 15, 0.95)';
});
.spotlight-container {
position: relative;
overflow: hidden;
}
.spotlight-overlay {
position: absolute;
inset: 0;
background: rgba(10, 10, 15, 0.95);
pointer-events: none;
transition: background 0.1s;
}
5. Cursor with blend mode
Blend mode allows the cursor to interact with the page's colors. With mix-blend-mode: difference, the cursor inverts the colors beneath it, creating a striking visual effect.
Observe how the cursor adapts to the background
const cursor = document.querySelector('.cursor-blend');
let cursorX = 0, cursorY = 0;
let targetX = 0, targetY = 0;
document.addEventListener('mousemove', (e) => {
targetX = e.clientX;
targetY = e.clientY;
});
function animateCursor() {
cursorX += (targetX - cursorX) * 0.1;
cursorY += (targetY - cursorY) * 0.1;
cursor.style.left = cursorX + 'px';
cursor.style.top = cursorY + 'px';
requestAnimationFrame(animateCursor);
}
animateCursor();
// Enlarge on link hover
document.querySelectorAll('a, button').forEach(el => {
el.addEventListener('mouseenter', () => {
cursor.style.transform = 'translate(-50%, -50%) scale(2)';
});
el.addEventListener('mouseleave', () => {
cursor.style.transform = 'translate(-50%, -50%) scale(1)';
});
});
.cursor-blend {
position: fixed;
width: 40px;
height: 40px;
background: #fff;
border-radius: 50%;
pointer-events: none;
z-index: 9999;
transform: translate(-50%, -50%);
mix-blend-mode: difference;
transition: transform 0.2s ease;
}
Best practices and accessibility
Custom cursors add style, but can harm the experience if poorly implemented. Here are the essential rules.
Performance
- Use
requestAnimationFrameinstead ofsetIntervalfor smooth animations - Prefer
transformoverleft/topfor animations (GPU accelerated) - Limit the number of particles for the trail effect (15-25 max)
- Use
will-change: transformsparingly
Accessibility
- Never completely hide the cursor over selectable text areas
- Maintain sufficient contrast between the cursor and the background
- Respect
prefers-reduced-motionfor animations
/* Disable on mobile */
@media (hover: none) {
.cursor-dot,
.cursor-circle,
.cursor-blend {
display: none;
}
body {
cursor: auto;
}
}
/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
.cursor-dot,
.cursor-circle {
transition: none;
}
.trail-particle {
display: none;
}
}
A cursor that is too large or too slow can frustrate the user. Always test with real users and keep the effect subtle.
Conclusion
Custom cursors are an excellent way to differentiate your site and create a memorable experience. With these 5 techniques, you have a solid foundation to experiment with.
Start with the simple cursor (dot + circle), then explore more advanced variations depending on your project's style. And don't forget: subtlety is the key to a good cursor effect.
Find complete templates with integrated cursors in our effects library.