Custom Cursor

A custom cursor component that follows the mouse cursor.

Emoji Cursor

Loading...

Text Cursor

Loading...

Custom Cursor

Loading...

Usage Guide

Install the clsx and tailwind-merge packages:

npm install clsx tw-merge

Next, add cn utility:

import { twMerge } from "tailwind-merge";
import { clsx, type ClassValue } from "clsx";

export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}

Copy and paste the following core component source for custom cursor:

"use client";

import { cn } from "@/lib/utils";
import React, { useState, useEffect, useRef } from "react";

interface CursorProps {
    children: React.ReactNode;
    cursor: string | React.ReactNode;
    className?: string;
}

export default function Cursor({ children, cursor, className }: CursorProps) {
    const containerRef = useRef<HTMLDivElement>(null);
    const [isHovering, setIsHovering] = useState(false);
    const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 });

    useEffect(() => {
        const updateCursorPosition = (e: MouseEvent) => {
            if (isHovering) {
                const rect = containerRef.current?.getBoundingClientRect();
                if (rect) {
                    setCursorPosition({
                        x: e.clientX - rect.left,
                        y: e.clientY - rect.top
                    });
                }
            }
        };

        document.addEventListener("mousemove", updateCursorPosition);

        return () => {
            document.removeEventListener("mousemove", updateCursorPosition);
        };
    }, [isHovering]);

    const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
        const rect = containerRef.current?.getBoundingClientRect();
        if (rect) {
            const entryX = e.clientX - rect.left;
            const entryY = e.clientY - rect.top;
            setCursorPosition({ x: entryX, y: entryY });
        }
        setIsHovering(true);
    };

    const handleMouseLeave = () => setIsHovering(false);

    return (
        <div
            ref={containerRef}
            onMouseEnter={handleMouseEnter}
            onMouseLeave={handleMouseLeave}
            className={cn("relative", className)}
            style={{ cursor: isHovering ? "none" : "default" }}
        >
            {children}
            {isHovering && (
                <div
                    className="absolute pointer-events-none z-50, whitespace-nowrap"
                    style={{
                        left: cursorPosition.x,
                        top: cursorPosition.y,
                        transform: "translate(-50%, -50%)",
                        transformOrigin: "left center"
                    }}
                >
                    {cursor}
                </div>
            )}
        </div>
    );
}

Props

PropTypeDescriptionDefault
childrenReactNodeThe content to be rendered inside the component_
cursorReactNodeA component to be shown as cursor_
classNamestringString of class_