Docs
Magic Card

Magic Card

A spotlight effect that follows your mouse cursor and highlights borders on hover.

Magic

Card

Demo

Installation

Copy and paste the following code into your project.

components/magicui/magic-card.tsx
"use client";
 
import clsx, { ClassValue } from "clsx";
import {
  CSSProperties,
  ReactElement,
  ReactNode,
  useEffect,
  useRef,
  useState,
} from "react";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
 
interface MousePosition {
  x: number;
  y: number;
}
 
function useMousePosition(): MousePosition {
  const [mousePosition, setMousePosition] = useState<MousePosition>({
    x: 0,
    y: 0,
  });
 
  useEffect(() => {
    const handleMouseMove = (event: globalThis.MouseEvent) => {
      setMousePosition({ x: event.clientX, y: event.clientY });
    };
 
    window.addEventListener("mousemove", handleMouseMove);
 
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, []);
 
  return mousePosition;
}
 
interface MagicContainerProps {
  children?: ReactNode;
  className?: any;
}
 
const MagicContainer = ({ children, className }: MagicContainerProps) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const mousePosition = useMousePosition();
  const mouse = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
  const containerSize = useRef<{ w: number; h: number }>({ w: 0, h: 0 });
  const [boxes, setBoxes] = useState<Array<HTMLElement>>([]);
 
  useEffect(() => {
    init();
    containerRef.current &&
      setBoxes(
        Array.from(containerRef.current.children).map(
          (el) => el as HTMLElement,
        ),
      );
  }, []);
 
  useEffect(() => {
    init();
    window.addEventListener("resize", init);
 
    return () => {
      window.removeEventListener("resize", init);
    };
  }, [setBoxes]);
 
  useEffect(() => {
    onMouseMove();
  }, [mousePosition]);
 
  const init = () => {
    if (containerRef.current) {
      containerSize.current.w = containerRef.current.offsetWidth;
      containerSize.current.h = containerRef.current.offsetHeight;
    }
  };
 
  const onMouseMove = () => {
    if (containerRef.current) {
      const rect = containerRef.current.getBoundingClientRect();
      const { w, h } = containerSize.current;
      const x = mousePosition.x - rect.left;
      const y = mousePosition.y - rect.top;
      const inside = x < w && x > 0 && y < h && y > 0;
 
      mouse.current.x = x;
      mouse.current.y = y;
      boxes.forEach((box) => {
        const boxX =
          -(box.getBoundingClientRect().left - rect.left) + mouse.current.x;
        const boxY =
          -(box.getBoundingClientRect().top - rect.top) + mouse.current.y;
        box.style.setProperty("--mouse-x", `${boxX}px`);
        box.style.setProperty("--mouse-y", `${boxY}px`);
 
        if (inside) {
          box.style.setProperty("--opacity", `1`);
        } else {
          box.style.setProperty("--opacity", `0`);
        }
      });
    }
  };
 
  return (
    <div className={cn("h-full w-full", className)} ref={containerRef}>
      {children}
    </div>
  );
};
 
interface MagicCardProps {
  /**
   * @default <div />
   * @type ReactElement
   * @description
   * The component to be rendered as the card
   * */
  as?: ReactElement;
  /**
   * @default ""
   * @type string
   * @description
   * The className of the card
   */
  className?: string;
 
  /**
   * @default ""
   * @type ReactNode
   * @description
   * The children of the card
   * */
  children?: ReactNode;
 
  /**
   * @default 600
   * @type number
   * @description
   * The size of the spotlight effect in pixels
   * */
  size?: number;
 
  /**
   * @default true
   * @type boolean
   * @description
   * Whether to show the spotlight
   * */
  spotlight?: boolean;
 
  /**
   * @default "rgba(255,255,255,0.03)"
   * @type string
   * @description
   * The color of the spotlight
   * */
  spotlightColor?: string;
 
  /**
   * @default true
   * @type boolean
   * @description
   * Whether to isolate the card which is being hovered
   * */
  isolated?: boolean;
 
  /**
   * @default "rgba(255,255,255,0.03)"
   * @type string
   * @description
   * The background of the card
   * */
  background?: string;
 
  [key: string]: any;
}
 
const MagicCard: React.FC<MagicCardProps> = ({
  className,
  children,
  size = 600,
  spotlight = true,
  borderColor = "hsl(0 0% 98%)",
  isolated = true,
  ...props
}) => {
  return (
    <div
      style={
        {
          "--mask-size": `${size}px`,
          "--border-color": `${borderColor}`,
        } as CSSProperties
      }
      className={cn(
        "relative z-0 h-full w-full rounded-2xl p-6",
        "bg-gray-300 dark:bg-gray-700",
        "bg-[radial-gradient(var(--mask-size)_circle_at_var(--mouse-x)_var(--mouse-y),var(--border-color),transparent_100%)]",
        className,
      )}
      {...props}
    >
      {children}
 
      {/* Background */}
      <div
        className={
          "absolute inset-[1px] -z-20 rounded-2xl bg-white dark:bg-black/95"
        }
      />
    </div>
  );
};
 
export { MagicCard, MagicContainer };

Examples

Radial Gradient border

Magic

Card

Demo

Props

MagicContainer

Prop nameTypeDefaultDescription
childrenReactNode-The children to render
classNamestring-The className to apply to the container

MagicCard

Prop nameTypeDefaultDescription
childrenReactNode-The children to render
classNamestring-The className to apply to the card
maskSizenumber200The size of the spotlight mask
borderColorstringrgba(120,119,198)The color of the border
borderWidthnumber1The width of the border
borderRadiusnumber16The radius of the border
spotlightbooleantrueWhether to show the spotlight
spotlightColorstringrgba(120,119,198,0.08)The color of the spotlight
isolatedbooleantrueWhether to isolate the card
backgroundstringrgba(120,119,198, 0.2)The background of the card

Credits

This component is inspired by Linear.app