Cursor

An animated cursor component that allows you to customize both the cursor and cursor follow elements with smooth animations.

Made by Léo · Credits: imskyleen
Open in

Move your mouse over the div

demo-cursor.tsx
import {
  Cursor,
  CursorFollow,
  CursorProvider,
  type CursorFollowProps,
} from "@/components/unlumen-ui/cursor";

interface CursorDemoProps {
  global?: boolean;
  enableCursor?: boolean;
  enableCursorFollow?: boolean;
  side?: CursorFollowProps["side"];
  sideOffset?: number;
  align?: CursorFollowProps["align"];
  alignOffset?: number;
}

export const CursorDemo = ({
  global = false,
  enableCursor = true,
  enableCursorFollow = true,
  side = "bottom",
  sideOffset = 15,
  align = "end",
  alignOffset = 5,
}: CursorDemoProps) => {
  return (
    <div
      key={String(global)}
      className="max-w-[400px] h-[400px] w-full bg-accent rounded-lg flex items-center justify-center"
    >
      <p className="font-medium italic text-muted-foreground">
        Move your mouse over the div
      </p>
      <CursorProvider global={global}>
        {enableCursor && <Cursor />}
        {enableCursorFollow && (
          <CursorFollow
            side={side}
            sideOffset={sideOffset}
            align={align}
            alignOffset={alignOffset}
          >
            Designer
          </CursorFollow>
        )}
      </CursorProvider>
    </div>
  );
};

Installation

Install the following registry dependencies:

Copy and paste the following code into your project:

components/unlumen-ui/cursor.tsx
import * as React from "react";
import {
  motion,
  useMotionValue,
  useSpring,
  useVelocity,
  useTransform,
} from "motion/react";

import {
  CursorProvider as CursorProviderPrimitive,
  Cursor as CursorPrimitive,
  CursorFollow as CursorFollowPrimitive,
  CursorContainer as CursorContainerPrimitive,
  useCursor,
  type CursorProviderProps as CursorProviderPropsPrimitive,
  type CursorContainerProps as CursorContainerPropsPrimitive,
  type CursorProps as CursorPropsPrimitive,
  type CursorFollowProps as CursorFollowPropsPrimitive,
} from "@/components/unlumen-ui/primitives/animate/cursor";
import { cn } from "@/lib/utils";

type CursorProviderProps = Omit<CursorProviderPropsPrimitive, "children"> &
  CursorContainerPropsPrimitive;

function CursorProvider({ global, ...props }: CursorProviderProps) {
  return (
    <CursorProviderPrimitive global={global}>
      <CursorContainerPrimitive {...props} />
    </CursorProviderPrimitive>
  );
}

type CursorProps = Omit<CursorPropsPrimitive, "children" | "asChild">;

function Cursor({ className, ...props }: CursorProps) {
  const { cursorPos } = useCursor();
  const x = useMotionValue(0);
  const y = useMotionValue(0);

  React.useEffect(() => {
    x.set(cursorPos.x);
    y.set(cursorPos.y);
  }, [cursorPos, x, y]);

  const springConfig = { damping: 25, stiffness: 300 };
  const smoothX = useSpring(x, springConfig);
  const smoothY = useSpring(y, springConfig);

  const velocityX = useVelocity(smoothX);
  const velocityY = useVelocity(smoothY);

  // tilt the cursor based on velocity for a gravity feel
  const rotateVelocity = useTransform([velocityX, velocityY], ([vx, vy]) => {
    const rx = ((vx as number) / 1000) * 30;
    const ry = ((vy as number) / 1000) * 30;
    return Math.max(-45, Math.min(45, rx + ry));
  });
  const rotate = useSpring(rotateVelocity, { damping: 15, stiffness: 200 });

  const scale = useTransform([velocityX, velocityY], ([vx, vy]) => {
    const velocity = Math.sqrt((vx as number) ** 2 + (vy as number) ** 2);
    return 1 - Math.min(velocity / 2000, 0.1);
  });

  return (
    <CursorPrimitive
      style={{
        top: smoothY,
        left: smoothX,
        transform: "translate(-4.5%, -11%)", // Override default centering to align tip
      }}
      {...props}
    >
      <motion.svg
        className={cn("size-6 text-foreground", className)}
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 40 40"
        style={{
          rotate,
          scale,
          transformOrigin: "4.5% 11%", // Tip of the SVG path
        }}
      >
        <path
          fill="currentColor"
          d="M1.8 4.4 7 36.2c.3 1.8 2.6 2.3 3.6.8l3.9-5.7c1.7-2.5 4.5-4.1 7.5-4.3l6.9-.5c1.8-.1 2.5-2.4 1.1-3.5L5 2.5c-1.4-1.1-3.5 0-3.3 1.9Z"
        />
      </motion.svg>
    </CursorPrimitive>
  );
}

type CursorFollowProps = Omit<CursorFollowPropsPrimitive, "asChild">;

function CursorFollow({
  className,
  children,
  sideOffset = 15,
  alignOffset = 5,
  ...props
}: CursorFollowProps) {
  const { cursorPos } = useCursor();
  const x = useMotionValue(0);
  const y = useMotionValue(0);

  React.useEffect(() => {
    x.set(cursorPos.x);
    y.set(cursorPos.y);
  }, [cursorPos, x, y]);

  const springConfig = { damping: 25, stiffness: 300 };
  const smoothX = useSpring(x, springConfig);
  const smoothY = useSpring(y, springConfig);

  const velocityX = useVelocity(smoothX);
  const velocityY = useVelocity(smoothY);

  const scaleX = useTransform(velocityX, [-1000, 0, 1000], [0.9, 1, 1.15]);
  const scaleY = useTransform(velocityY, [-1000, 0, 1000], [1.15, 1, 0.9]);

  const skewX = useTransform(velocityX, [-1000, 0, 1000], [-3, 0, 3]);
  const skewY = useTransform(velocityY, [-1000, 0, 1000], [-3, 0, 3]);

  return (
    <CursorFollowPrimitive
      sideOffset={sideOffset}
      alignOffset={alignOffset}
      {...props}
    >
      <motion.div
        className={cn(
          "bg-foreground rounded-md text-background px-2 py-1 text-sm",
          className,
        )}
        style={{
          scaleX,
          scaleY,
          skewX,
          skewY,
        }}
      >
        {children}
      </motion.div>
    </CursorFollowPrimitive>
  );
}

export {
  CursorProvider,
  Cursor,
  CursorFollow,
  type CursorProviderProps,
  type CursorProps,
  type CursorFollowProps,
};

Update the import paths to match your project setup.

Usage

<CursorProvider>
  <Cursor />
  <CursorFollow>Designer</CursorFollow>
</CursorProvider>

API Reference

CursorProvider

Unlumen Ui API Reference - CursorProvider Primitive
PropTypeDefault
children
React.ReactNode
-
global?
boolean
false
...props?
HTMLMotionProps<"div">
-

Cursor

Unlumen Ui API Reference - Cursor Primitive
PropTypeDefault
asChild?
boolean
false
...props?
HTMLMotionProps<"div">
-

CursorFollow

Unlumen Ui API Reference - CursorFollow Primitive
PropTypeDefault
asChild?
boolean
false
side?
CursorFollowSide
bottom
sideOffset?
number
15
align?
CursorFollowAlign
end
alignOffset?
number
5
transition?
SpringOptions
{ stiffness: 500, damping: 50, bounce: 0 }
...props?
HTMLMotionProps<"div">
-

Built by Léo from Unlumen :3

Last updated: 3/15/2026