Build websites 10x faster with HextaUI Blocks — Learn more
Original Blocks/Animations

Spotlight Card

Create a spotlight effect on hover on a card component.

Lighting Fast

Optimized for performance with minimal bundle size. Build fast, responsive websites without compromise.

Lighting Fast

Optimized for performance with minimal bundle size. Build fast, responsive websites without compromise.

Installation

Copy and paste the following code into your project.

components/ui/spotlight-card.tsx
"use client";
import { useRef, useState, useEffect } from "react";

export const SpotlightCard = ({
  children,
  className = "",
  spotlightColor = "#6300ff30",
  lightThemeSpotlightColor,
  darkThemeSpotlightColor,
}: {
  children: any;
  className?: string;
  spotlightColor?: string;
  lightThemeSpotlightColor?: string;
  darkThemeSpotlightColor?: string;
}) => {
  const divRef = useRef<HTMLDivElement>(null);
  const [isFocused, setIsFocused] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [opacity, setOpacity] = useState(0);
  const [currentTheme, setCurrentTheme] = useState<"light" | "dark">("dark");

  useEffect(() => {
    const detectTheme = () => {
      if (!lightThemeSpotlightColor && !darkThemeSpotlightColor) return;

      const isDarkClass =
        document.documentElement.classList.contains("dark") ||
        document.body.classList.contains("dark");

      const computedStyle = getComputedStyle(document.documentElement);
      const bgColor =
        computedStyle.getPropertyValue("--background") ||
        computedStyle.backgroundColor;

      const isDarkMedia = window.matchMedia(
        "(prefers-color-scheme: dark)",
      ).matches;

      const isDark =
        isDarkClass ||
        bgColor.includes("0 0% 3.9%") ||
        (!isDarkClass && isDarkMedia);

      setCurrentTheme(isDark ? "dark" : "light");
    };

    detectTheme();

    const observer = new MutationObserver(detectTheme);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class", "data-theme"],
    });

    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    mediaQuery.addEventListener("change", detectTheme);

    return () => {
      observer.disconnect();
      mediaQuery.removeEventListener("change", detectTheme);
    };
  }, [lightThemeSpotlightColor, darkThemeSpotlightColor]);

  const getSpotlightColor = () => {
    if (currentTheme === "light" && lightThemeSpotlightColor) {
      return lightThemeSpotlightColor;
    }
    if (currentTheme === "dark" && darkThemeSpotlightColor) {
      return darkThemeSpotlightColor;
    }
    return spotlightColor;
  };
  const handleMouseMove = (e: { clientX: number; clientY: number }) => {
    if (!divRef.current || isFocused) return;
    const rect = divRef.current.getBoundingClientRect();
    setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
  };
  const handleFocus = () => {
    setIsFocused(true);
    setOpacity(0.6);
  };
  const handleBlur = () => {
    setIsFocused(false);
    setOpacity(0);
  };
  const handleMouseEnter = () => {
    setOpacity(0.6);
  };
  const handleMouseLeave = () => {
    setOpacity(0);
  };

  return (
    <div
      ref={divRef}
      onMouseMove={handleMouseMove}
      onFocus={handleFocus}
      onBlur={handleBlur}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      className={`relative rounded-[var(--)] border border-[hsl(var(--hu-border))] bg-[hsl(var(--hu-card))] overflow-hidden p-8 ${className}`}
    >
      {" "}
      <div
        className="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-500 ease-in-out"
        style={{
          opacity,
          background: `radial-gradient(circle at ${position.x}px ${
            position.y
          }px, ${getSpotlightColor()}, transparent 80%)`,
        }}
      />
      {children}
    </div>
  );
};
npx shadcn@latest add "https://21st.dev/r/hextaui/spotlight-card"
pnpm dlx shadcn@latest add "https://21st.dev/r/hextaui/spotlight-card"
yarn dlx shadcn@latest add "https://21st.dev/r/hextaui/spotlight-card"
bun x shadcn@latest add "https://21st.dev/r/hextaui/spotlight-card"

Usage

import { SpotlightCard } from "@/components/ui/spotlight-card";
<SpotlightCard
  className="magic-card flex flex-col gap-4 max-w-[30rem] rounded-[var(--radius)] bg-[hsl(var(--hu-card))] border border-[hsl(var(--hu-border))]"
  spotlightColor="#ffffff30"
  lightThemeSpotlightColor="#00000020"
  darkThemeSpotlightColor="#ffffff30"
>
  <div className="text-2xl font-bold flex items-center gap-2">
    <FaBolt className="text-yellow-500" />
    <span>Lighting Fast</span>
  </div>
  <div className="text-muted-foreground">
    Optimized for performance with minimal bundle size. Build fast, responsive
    websites without compromise.
  </div>
</SpotlightCard>

Warning

Make sure to add magic-card class to the card component to enable the spotlight effect. This class is essential for the animation to work correctly.

<SpotlightCard
  className="magic-card"
  spotlightColor="#ffffff30"
  lightThemeSpotlightColor="#00000020"
  darkThemeSpotlightColor="#ffffff30"
>
  {/** Card content*/}
</SpotlightCard>

Props

PropTypeDefault
darkThemeSpotlightColor?
string
-
lightThemeSpotlightColor?
string
-
spotlightColor?
string
#6300ff30
className?
string
-
children?
ReactNode
-
Edit on GitHub

Last updated on