my/ui

Command Palette

Search for a command to run...

All components

Spotlight Card

cards

A card with a cursor-following radial spotlight over its surface.

responsive · 480px

Install

Same command in any shadcn project — React (Vite/CRA), Next.js, Remix, Astro, and more:

$npx shadcn@latest add https://your-domain/r/spotlight-card.json

Usage

import { Sparkles } from "lucide-react";
import { SpotlightCard } from "@/registry/cards/spotlight-card";

export default function SpotlightCardDemo() {
  return (
    <SpotlightCard className="max-w-xs">
      <Sparkles className="size-5 text-muted-foreground" />
      <h3 className="mt-3 text-base font-semibold">Spotlight Card</h3>
      <p className="mt-1 text-sm text-muted-foreground">
        Move your cursor across the card to reveal the spotlight that tracks your pointer.
      </p>
    </SpotlightCard>
  );
}

Component source

"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

export interface SpotlightCardProps
  extends React.ComponentPropsWithoutRef<"div"> {
  /** Radius of the spotlight in pixels. */
  radius?: number;
  /** Spotlight color (any valid CSS color). */
  color?: string;
}

/**
 * A card with a cursor-following radial spotlight on its border/surface.
 * The spotlight is driven by CSS variables updated on pointer move.
 */
export const SpotlightCard = React.forwardRef<
  HTMLDivElement,
  SpotlightCardProps
>(
  (
    {
      radius = 350,
      color = "color-mix(in oklch, var(--foreground) 12%, transparent)",
      className,
      children,
      ...props
    },
    ref,
  ) => {
    const localRef = React.useRef<HTMLDivElement>(null);
    React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);
    const [active, setActive] = React.useState(false);

    const onMove = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
      const el = localRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      el.style.setProperty("--x", `${e.clientX - rect.left}px`);
      el.style.setProperty("--y", `${e.clientY - rect.top}px`);
    }, []);

    return (
      <div
        ref={localRef}
        onMouseMove={onMove}
        onMouseEnter={() => setActive(true)}
        onMouseLeave={() => setActive(false)}
        style={
          {
            "--radius": `${radius}px`,
            "--spotlight": color,
          } as React.CSSProperties
        }
        className={cn(
          "group relative overflow-hidden rounded-xl border border-border bg-card p-6 text-card-foreground shadow-sm",
          className,
        )}
        {...props}
      >
        <div
          aria-hidden
          className="pointer-events-none absolute inset-0 transition-opacity duration-300"
          style={{
            opacity: active ? 1 : 0,
            background:
              "radial-gradient(var(--radius) circle at var(--x) var(--y), var(--spotlight), transparent 70%)",
          }}
        />
        <div className="relative">{children}</div>
      </div>
    );
  },
);

SpotlightCard.displayName = "SpotlightCard";