Select Page

How to Create a Custom Cursor in Next.js

I feel like a custom cursor makes a website feel different. It turns a boring pointer arrow into something kind of engaging. While they might seem fancy, they’re actually pretty simple to build in Next.js.

Why Add a Custom Cursor?

Regular cursors work fine, but custom cursors add personality to your website. They can be emojis, images, or even animated shapes that follow your mouse. It’s like giving your website its own style of handwriting instead of using the same font as everyone else.

A custom cursor can make your website feel more interactive and memorable. Think about it – when was the last time you saw a cursor that made you smile?

Types of Cursors in Next.js

Next.js gives you several cursor options right out of the box:

  1. Default Arrow: The regular pointer we all know
  2. Text Cursor: The I-beam that shows up when you’re typing
  3. Pointer: The hand that appears when you hover over links
  4. Wait: The spinning circle for loading
  5. Not-Allowed: The circle with a line through it
  6. Resize: Arrows that show up when you can adjust sizes
  7. Grab/Grabbing: For when users can drag things around

These built-in cursors work great for most websites. But sometimes you want something totally different, like an emoji or a custom design. That’s where custom cursors come in.

Think of custom cursors as your website’s personality showing through. They can be playful, professional, or completely wild – it’s up to you.

Building a Custom Cursor

Let’s build a custom cursor that follows your mouse around the screen. We’ll use a thumbs-up emoji as an example, but you can use any emoji or design you want.

The cool thing about this approach is that it works perfectly with Next.js 15’s new features while being simple enough to understand.

Here’s the complete code first, then we’ll break it down:

javascript
'use client'
import { useEffect, useState } from 'react';

const CustomCursor = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const updatePosition = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };

    const section = document.querySelector('.custom-cursor');
    const handleMouseEnter = () => setIsVisible(true);
    const handleMouseLeave = () => setIsVisible(false);

    section?.addEventListener('mouseenter', handleMouseEnter);
    section?.addEventListener('mouseleave', handleMouseLeave);
    window.addEventListener('mousemove', updatePosition);

    return () => {
      section?.removeEventListener('mouseenter', handleMouseEnter);
      section?.removeEventListener('mouseleave', handleMouseLeave);
      window.removeEventListener('mousemove', updatePosition);
    };
  }, []);

  if (!isVisible) return null;

  return (
    <div 
      style={{
        position: 'fixed',
        left: position.x,
        top: position.y,
        pointerEvents: 'none',
        zIndex: 9999,
        fontSize: '24px',
        transform: 'translate(-50%, -50%)'
      }}
    >
      👍
    </div>
  );
};

export default CustomCursor;

Now let’s break down what each part does:

javascript
'use client'
import { useEffect, useState } from 'react';

We start with ‘use client’ because our cursor needs to run in the browser. We also import two React hooks: useEffect for setting up our mouse tracking, and useState for storing the cursor’s position.

javascript
const CustomCursor = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isVisible, setIsVisible] = useState(false);

We create two pieces of state:

  • position: Tracks where the cursor is on the screen
  • isVisible: Controls when to show or hide the cursor
javascript
  useEffect(() => {
    const updatePosition = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };

This function updates our cursor position whenever the mouse moves. clientX and clientY tell us exactly where the mouse is on the screen.

javascript
    const section = document.querySelector('.custom-cursor');
    const handleMouseEnter = () => setIsVisible(true);
    const handleMouseLeave = () => setIsVisible(false);

These lines control when the cursor shows up. It only appears when your mouse is over elements with the ‘custom-cursor’ class.

javascript
    section?.addEventListener('mouseenter', handleMouseEnter);
    section?.addEventListener('mouseleave', handleMouseLeave);
    window.addEventListener('mousemove', updatePosition);

Here we set up three event listeners:

  • mouseenter: Shows the cursor when you move into the right area
  • mouseleave: Hides the cursor when you leave
  • mousemove: Updates the cursor position as you move
javascript
    return () => {
      section?.removeEventListener('mouseenter', handleMouseEnter);
      section?.removeEventListener('mouseleave', handleMouseLeave);
      window.removeEventListener('mousemove', updatePosition);
    };
  }, []);

This cleanup function removes our event listeners when we’re done. It prevents memory leaks and keeps our website running smoothly.

javascript
  if (!isVisible) return null;
  return (
    <div 
      style={{
        position: 'fixed',
        left: position.x,
        top: position.y,
        pointerEvents: 'none',
        zIndex: 9999,
        fontSize: '24px',
        transform: 'translate(-50%, -50%)'
      }}
    >
      👍
    </div>
  );
};

Finally, we create the cursor itself. The styles make sure it:

  • Stays fixed on the screen
  • Follows the mouse position
  • Doesn’t interfere with clicking
  • Shows up on top of other content
  • Centers perfectly on the mouse position

To use this cursor, add it to your page and put the ‘custom-cursor’ class on any element where you want it to appear:

javascript
export default function Page() {
  return (
    <div className="custom-cursor">
      <CustomCursor />
      <h1>Welcome to my website!</h1>
    </div>
  );
}

That’s all it takes to add a custom cursor to your Next.js website. You can swap out the thumbs-up emoji for any design you want, adjust the size, or add animations to make it even more unique.