Layout
Spotlight Card
A card with a radial glow that follows the pointer across its surface.
Edge rendering
Move your cursor across the card — the glow tracks it.
Custom color
Pass any CSS color to tint the spotlight.
import { SpotlightCard } from "@/components/ui/spotlight-card";
export default function SpotlightCardDemo() {
return (
<div className="grid w-full max-w-2xl gap-4 sm:grid-cols-2">
<SpotlightCard>
<h3 className="text-sm font-semibold">Edge rendering</h3>
<p className="mt-2 text-sm text-muted">
Move your cursor across the card — the glow tracks it.
</p>
</SpotlightCard>
<SpotlightCard color="oklch(72% 0.17 150)">
<h3 className="text-sm font-semibold">Custom color</h3>
<p className="mt-2 text-sm text-muted">
Pass any CSS color to tint the spotlight.
</p>
</SpotlightCard>
</div>
);
}Installation
1. Install the dependencies.
terminal npm install clsx tailwind-merge2. Add the
cnhelper, if you don't already have it.lib/utils.ts import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }3. Copy the component into your project.
components/ui/spotlight-card.tsx "use client"; import { useCallback, useRef, useState, type ReactNode } from "react"; import { cn } from "@/lib/utils"; type SpotlightCardProps = { children: ReactNode; className?: string; /** Radius of the cursor glow, in pixels. */ radius?: number; /** Any CSS color. Defaults to the theme accent. */ color?: string; }; export function SpotlightCard({ children, className, radius = 320, color = "var(--color-accent)", }: SpotlightCardProps) { const ref = useRef<HTMLDivElement>(null); const [position, setPosition] = useState({ x: 0, y: 0 }); const [opacity, setOpacity] = useState(0); const handleMove = useCallback((event: React.MouseEvent<HTMLDivElement>) => { const bounds = event.currentTarget.getBoundingClientRect(); setPosition({ x: event.clientX - bounds.left, y: event.clientY - bounds.top, }); }, []); return ( <div ref={ref} onMouseMove={handleMove} onMouseEnter={() => setOpacity(1)} onMouseLeave={() => setOpacity(0)} className={cn( "group relative overflow-hidden rounded-card border border-line bg-surface p-6", "transition-colors duration-300 hover:border-accent/40", className, )} > <div aria-hidden className="pointer-events-none absolute inset-0 transition-opacity duration-500" style={{ opacity, background: `radial-gradient(${radius}px circle at ${position.x}px ${position.y}px, color-mix(in oklab, ${color} 22%, transparent), transparent 72%)`, }} /> <div className="relative">{children}</div> </div> ); }
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Card contents. |
| radius | number | 320 | Radius of the cursor glow, in pixels. |
| color | string | var(--color-accent) | Any CSS color for the spotlight tint. |
| className | string | — | Merged onto the card. |