A cursor-following spotlight effect that reveals a soft glowing circle wherever the user moves their mouse inside a container.
Made by LéoLoading...
"use client";
import { ClippedCircle } from "@/components/unlumen-ui/primitives/clipped-circle";
import { Button } from "@/components/ui/button";
interface ClippedCircleDemoProps {
circleSize: number;
}
export default function ClippedCircleDemo({
circleSize = 400,
}: ClippedCircleDemoProps) {
const actions = [
{ label: "Docs", variant: "default" },
{ label: "Install", variant: "outline" },
{ label: "Components", variant: "ghost" },
] as const;
return (
<div className="mx-auto flex w-full max-w-xl flex-col gap-5 p-6">
<div className="grid grid-cols-3 gap-3">
{actions.map(({ label, variant }) => (
<Button
key={label}
variant={variant}
size="sm"
className="relative overflow-hidden"
>
{label}
<ClippedCircle circleSize={260} circleClassName="bg-white" />
</Button>
))}
</div>
<div className="relative min-h-75 overflow-hidden rounded-2xl border border-border/70 bg-[radial-gradient(circle_at_top_left,hsl(var(--primary)/0.16),transparent_38%),linear-gradient(135deg,hsl(var(--muted)),hsl(var(--background)))] p-8 shadow-sm">
<div className="flex h-full min-h-59 items-center justify-center rounded-xl border border-border/60 bg-white p-10 shadow-inner">
<img
src="/docs/unlumen-ui.svg"
alt="Unlumen UI"
className="h-auto w-full max-w-52 object-contain"
/>
</div>
<ClippedCircle circleSize={circleSize} circleClassName="bg-white" />
</div>
</div>
);
}Installation
Install the following dependencies:
Copy and paste the following code into your project:
"use client";
import * as React from "react";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";
interface ClippedCircleProps {
className?: string;
circleClassName?: string;
circleSize?: number;
}
function ClippedCircle({
className,
circleClassName = "bg-white/20",
circleSize = 400,
}: ClippedCircleProps) {
const containerRef = React.useRef<HTMLDivElement>(null);
const [isHovered, setIsHovered] = React.useState(false);
const [position, setPosition] = React.useState({ x: "50%", y: "50%" });
React.useEffect(() => {
const container = containerRef.current;
if (!container || !container.parentElement) return;
const parent = container.parentElement;
const handleMouseEnter = (e: MouseEvent) => {
const rect = parent.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
setPosition({ x: `${x}%`, y: `${y}%` });
setIsHovered(true);
};
const handleMouseMove = (e: MouseEvent) => {
const rect = parent.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
setPosition({ x: `${x}%`, y: `${y}%` });
};
const handleMouseLeave = () => {
setIsHovered(false);
};
parent.addEventListener("mouseenter", handleMouseEnter);
parent.addEventListener("mousemove", handleMouseMove);
parent.addEventListener("mouseleave", handleMouseLeave);
return () => {
parent.removeEventListener("mouseenter", handleMouseEnter);
parent.removeEventListener("mousemove", handleMouseMove);
parent.removeEventListener("mouseleave", handleMouseLeave);
};
}, []);
return (
<div
ref={containerRef}
className={cn(
"absolute inset-0 overflow-hidden pointer-events-none",
className,
)}
>
<motion.div
className={cn(
"pointer-events-none absolute rounded-full",
circleClassName,
)}
style={{
left: position.x,
top: position.y,
width: circleSize,
height: circleSize,
mixBlendMode: "difference",
}}
initial={{ scale: 0, x: "-50%", y: "-50%" }}
animate={{
scale: isHovered ? 1 : 0,
x: "-50%",
y: "-50%",
}}
transition={{
duration: 0.5,
ease: [0.19, 1, 0.22, 1],
}}
/>
</div>
);
}
export { ClippedCircle, type ClippedCircleProps };Update the import paths to match your project setup.
Usage
Place ClippedCircle as a direct child of any relative overflow-hidden container. It attaches mouse listeners to its parent element automatically.
import { ClippedCircle } from "@/components/unlumen-ui/components/effects/clipped-circle";
// Inside a button
<button className="relative overflow-hidden ...">
Click me
<ClippedCircle circleSize={300} circleClassName="bg-white/25" />
</button>
// Inside a card
<div className="relative overflow-hidden ...">
<p>Card content</p>
<ClippedCircle circleSize={500} circleClassName="bg-violet-500/30" />
</div>API Reference
ClippedCircle
| Prop | Type | Default | Description |
|---|---|---|---|
circleSize? | number | 400 | Diameter of the spotlight circle in pixels. |
circleClassName? | string | "bg-white/20" | Tailwind class(es) applied to the circle element. Use bg-* classes to control color and opacity. |
className? | string | - | Class applied to the wrapper div (absolute inset-0). |
Built by Léo
Last updated: 5/14/2026