All components
Custom Cursor Follower
effectsAceternity UI component.
responsive · 600px
Install
Same command in any shadcn project — React (Vite/CRA), Next.js, Remix, Astro, and more:
$
npx shadcn@latest add https://your-domain/r/following-pointer.jsonUsage
import { FollowerPointerCard } from "@/registry/aceternity-ui/following-pointer";
export default function Demo() {
return (
<div className="flex h-full w-full items-center justify-center bg-background p-8">
<FollowerPointerCard
title="Read more →"
className="max-w-sm rounded-2xl border border-border bg-card shadow-md"
>
<div className="overflow-hidden rounded-t-2xl">
<div className="h-44 w-full bg-gradient-to-br from-violet-500 via-sky-400 to-emerald-400" />
</div>
<div className="p-5">
<span className="mb-2 inline-block rounded-full border border-border px-2.5 py-0.5 text-xs text-muted-foreground">
Design Systems
</span>
<h3 className="mb-1 text-lg font-semibold text-foreground">
Building with Motion
</h3>
<p className="text-sm text-muted-foreground">
Hover anywhere on this card — a custom pointer trails your cursor
with a coloured label in tow.
</p>
<div className="mt-4 flex items-center gap-2">
<div className="h-6 w-6 rounded-full bg-gradient-to-br from-orange-400 to-pink-500" />
<span className="text-xs text-muted-foreground">Jane Doe · 3 min read</span>
</div>
</div>
</FollowerPointerCard>
</div>
);
}Component source
import React, { useEffect, useState } from "react";
import { motion, AnimatePresence, useMotionValue } from "motion/react";
import { cn } from "@/lib/utils";
export const FollowerPointerCard = ({
children,
className,
title,
}: {
children: React.ReactNode;
className?: string;
title?: string | React.ReactNode;
}) => {
const x = useMotionValue(0);
const y = useMotionValue(0);
const ref = React.useRef<HTMLDivElement>(null);
const [rect, setRect] = useState<DOMRect | null>(null);
const [isInside, setIsInside] = useState<boolean>(false); // Add this line
useEffect(() => {
if (ref.current) {
setRect(ref.current.getBoundingClientRect());
}
}, []);
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (rect) {
const scrollX = window.scrollX;
const scrollY = window.scrollY;
x.set(e.clientX - rect.left + scrollX);
y.set(e.clientY - rect.top + scrollY);
}
};
const handleMouseLeave = () => {
setIsInside(false);
};
const handleMouseEnter = () => {
setIsInside(true);
};
return (
<div
onMouseLeave={handleMouseLeave}
onMouseEnter={handleMouseEnter}
onMouseMove={handleMouseMove}
style={{
cursor: "none",
}}
ref={ref}
className={cn("relative", className)}
>
<AnimatePresence>
{isInside && <FollowPointer x={x} y={y} title={title} />}
</AnimatePresence>
{children}
</div>
);
};
export const FollowPointer = ({
x,
y,
title,
}: {
x: any;
y: any;
title?: string | React.ReactNode;
}) => {
const colors = [
"#0ea5e9",
"#737373",
"#14b8a6",
"#22c55e",
"#3b82f6",
"#ef4444",
"#eab308",
];
return (
<motion.div
className="absolute z-50 h-4 w-4 rounded-full"
style={{
top: y,
left: x,
pointerEvents: "none",
}}
initial={{
scale: 1,
opacity: 1,
}}
animate={{
scale: 1,
opacity: 1,
}}
exit={{
scale: 0,
opacity: 0,
}}
>
<svg
stroke="currentColor"
fill="currentColor"
strokeWidth="1"
viewBox="0 0 16 16"
className="h-6 w-6 -translate-x-[12px] -translate-y-[10px] -rotate-[70deg] transform stroke-sky-600 text-sky-500"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M14.082 2.182a.5.5 0 0 1 .103.557L8.528 15.467a.5.5 0 0 1-.917-.007L5.57 10.694.803 8.652a.5.5 0 0 1-.006-.916l12.728-5.657a.5.5 0 0 1 .556.103z"></path>
</svg>
<motion.div
style={{
backgroundColor: colors[Math.floor(Math.random() * colors.length)],
}}
initial={{
scale: 0.5,
opacity: 0,
}}
animate={{
scale: 1,
opacity: 1,
}}
exit={{
scale: 0.5,
opacity: 0,
}}
className={
"min-w-max rounded-full bg-neutral-200 px-2 py-2 text-xs whitespace-nowrap text-white"
}
>
{title || `William Shakespeare`}
</motion.div>
</motion.div>
);
};Dependencies
motion
Source: Aceternity UI