Skip to content
jjswnth/ui

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.

Installation

  1. 1. Install the dependencies.

    terminal
    npm install clsx tailwind-merge
  2. 2. Add the cn helper, 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. 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

PropTypeDefaultDescription
childrenReactNodeCard contents.
radiusnumber320Radius of the cursor glow, in pixels.
colorstringvar(--color-accent)Any CSS color for the spotlight tint.
classNamestringMerged onto the card.