Spotlight Card

A container for interactive cards with a hover spotlight effect, ideal for showcasing features or highlights.

spotlight-card-preview.jsx

import SpotlightCard, { SpotlightCardsContainer }  from "@/components/interactions/spotlight-card/spotlight-card";

const SpotlightCardPreview = () => {
  return (
    <SpotlightCardsContainer
      style={spotlightCardsContainerStyles}
    >
      {cards.map((card) => (
        <SpotlightCard
          spotlightColor="rgb(132, 250, 175)"
          style={spotlightCardStyles}
          wrapperProps={{
            style: spotlightCardWrapperStyles,
          }}
        >
          <span style={cardIcon}>{card.icon}</span>
          <h2 style={cardHeading}>{card.title}</h2>
          <p>{card.description}</p>
        </SpotlightCard>
      ))}
    </SpotlightCardsContainer>
  );
};

const spotlightCardsContainerStyles = {
  margin: "32px",
  display: "flex",
  gap: "16px",
  flexWrap: "wrap",
  justifyContent: "center",
};

const spotlightCardStyles = {
  width: "320px",
  borderRadius: "8px",
};

const spotlightCardWrapperStyles = {
  background: "var(--layer-secondary, #111111)",
  padding: "16px",
};

const cardHeading = {
  fontSize: "1.25em",
};

const cardIcon = {
  display: "inline-block",
  marginBottom: "16px",
};

const cards = [
  {
    icon: "",
    title: "Fast Performance",
    description: "Experience lightning-fast load times and smooth interactions across all devices.",
  },
  {
    icon: "🔒",
    title: "Secure by Default",
    description: "Built with modern security practices to keep your data safe and protected.",
  },
  {
    icon: "🎨",
    title: "Customizable UI",
    description: "Easily adapt components to match your brand and design system.",
  },
  {
    icon: "📱",
    title: "Responsive Design",
    description: "Optimized layouts that look great on mobile, tablet, and desktop screens.",
  },
  {
    icon: "🧑‍💻",
    title: "Developer Friendly",
    description: "Simple APIs and flexible props make development fast and enjoyable.",
  },
  {
    icon: "📈",
    title: "Scalable Architecture",
    description: "Designed to grow with your application from small projects to large systems.",
  },
];

export default SpotlightCardPreview;

Installation

Run the command from your project root directory (the folder that contains package.json).

Terminal / Console

npx mosaicui-cli@latest interactions/spotlight-card

1. Copy the component file

Create a new file called spotlight-card.jsx in your reusable components folder (for example /src/components/) and paste the following code into it.

spotlight-card.jsx

import { memo, useMemo } from "react";
import styles from "./spotlight-card.module.css";

export const SpotlightCardsContainer = memo((props) => {
  const {
    children,
    className,
    tagName = "div",
    ...restProps
  } = props;

  const Component = tagName || "div";

  const handleMouseMove = (e) => {
    const { clientX, clientY } = e;
    const cards = e.currentTarget.querySelectorAll(`.${styles["spotlight-card"]}`);
    cards.forEach((card) => {
      const { x, y } = card.getBoundingClientRect();
      card.style.setProperty("--mx", `${clientX - x}px`);
      card.style.setProperty("--my", `${clientY - y}px`);
    });
  };

  return (
    <Component
      {...restProps}
      className={[
        styles["spotlight-card-container"],
        className,
      ].join(" ")}
      onMouseMove={handleMouseMove}
    >
      {children}
    </Component>
  );
});

const SpotlightCard = (props) => {
  const {
    children,
    spotlightColor = "rgb(127, 127, 127)",
    spotlightSize = 100,
    spotlightBorderWidth = 1,
    tagName = "div",
    className,
    style,
    wrapperTagName = "div",
    wrapperProps = {},
    ...restProps
  } = props;

  const Component = tagName || "div";

  const Wrapper = wrapperTagName || "div";

  const {
    className: wrapperClassName = "",
    ...restWrapperProps
  } = wrapperProps;

  const _spotlightBorderWidth = useMemo(() => (
    Math.max(0, spotlightBorderWidth)
  ), [spotlightBorderWidth]);

  const _spotlightColor = useMemo(() => {
    const canvas = document.createElement("canvas");
    canvas.width = canvas.height = 1;
    const ctx = canvas.getContext("2d");
    ctx.fillStyle = spotlightColor;
    ctx.fillRect(0, 0, 1, 1);
    const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data);
    canvas.remove();
    return (alpha) => {
      return `rgba(${r}, ${g}, ${b}, ${alpha})`;
    };
  }, [spotlightColor]);

  return (
    <Component
      {...restProps}
      className={[
        styles["spotlight-card"],
        className,
      ].join(" ")}
      style={{
        ...style,
        "--spotlight-size": `${spotlightSize}px`,
        "--spotlight-before-color": _spotlightColor(0.8),
        "--spotlight-after-color": _spotlightColor(0.2),
        "--spotlight-border-width": `${_spotlightBorderWidth}px`,
      }}
    >
      <Wrapper
        {...restWrapperProps}
        className={[
          styles["wrapper"],
          wrapperClassName,
        ].join(" ")}
      >
        {children}
      </Wrapper>
    </Component>
  );
};

export default memo(SpotlightCard);

2. Copy the CSS module file

In the same folder, create a file called spotlight-card.module.css and paste the following CSS.

spotlight-card.module.css

.spotlight-card-container {

  .spotlight-card {
    position: relative;

    .wrapper {
      position: relative;
      top: var(--spotlight-border-width) !important;
      left: var(--spotlight-border-width) !important;
      width: calc(100% - calc(var(--spotlight-border-width) * 2)) !important;
      height: calc(100% - calc(var(--spotlight-border-width) * 2)) !important; 
      border-radius: inherit;
      z-index: 2;
    }

    &:before,
    &:after {
      content: "";
      position: absolute;
      inset: 0;
      pointer-events: none;
      opacity: 0;
      border-radius: inherit;
      transition: all 250ms ease-in-out; 
    }

    &:before {
      background: radial-gradient(
        var(--spotlight-size) circle at var(--mx) var(--my),
        var(--spotlight-before-color),
        rgba(0, 0, 0, 0)
      );
      z-index: 1;
    }

    &:after {
      background: radial-gradient(
        var(--spotlight-size) circle at var(--mx) var(--my),
        var(--spotlight-after-color),
        rgba(0, 0, 0, 0)
      );
      z-index: 3;
    }

    &:hover:after {
      opacity: 1;
    }
  }
  &:hover {
    .spotlight-card:before {
      opacity: 1;
    }
  }
}

Examples

Discover how supported props transform components to match your needs.

Spotlight Size

spotlight-card

import SpotlightCard, { SpotlightCardsContainer }  from "@/components/interactions/spotlight-card/spotlight-card";

const SpotlightCardSpotlightSizePreview = () => {
  return (
    <SpotlightCardsContainer
      style={spotlightCardsContainerStyles}
    >
      {cards.map((card) => (
        <SpotlightCard
          spotlightColor="rgb(132, 250, 175)"
          spotlightSize={200}
          style={spotlightCardStyles}
          wrapperProps={{
            style: spotlightCardWrapperStyles,
          }}
        >
          <span style={cardIcon}>{card.icon}</span>
          <h2 style={cardHeading}>{card.title}</h2>
          <p>{card.description}</p>
        </SpotlightCard>
      ))}
    </SpotlightCardsContainer>
  );
};

const spotlightCardsContainerStyles = {
  margin: "32px",
  display: "flex",
  gap: "16px",
  flexWrap: "wrap",
  justifyContent: "center",
};

const spotlightCardStyles = {
  width: "320px",
  borderRadius: "8px",
};

const spotlightCardWrapperStyles = {
  background: "var(--layer-secondary, #111111)",
  padding: "16px",
};

const cardHeading = {
  fontSize: "1.25em",
};

const cardIcon = {
  display: "inline-block",
  marginBottom: "16px",
};

const cards = [
  {
    icon: "",
    title: "Fast Performance",
    description: "Experience lightning-fast load times and smooth interactions across all devices.",
  },
  {
    icon: "🔒",
    title: "Secure by Default",
    description: "Built with modern security practices to keep your data safe and protected.",
  },
  {
    icon: "🎨",
    title: "Customizable UI",
    description: "Easily adapt components to match your brand and design system.",
  },
  {
    icon: "📱",
    title: "Responsive Design",
    description: "Optimized layouts that look great on mobile, tablet, and desktop screens.",
  },
  {
    icon: "🧑‍💻",
    title: "Developer Friendly",
    description: "Simple APIs and flexible props make development fast and enjoyable.",
  },
  {
    icon: "📈",
    title: "Scalable Architecture",
    description: "Designed to grow with your application from small projects to large systems.",
  },
];

export default SpotlightCardSpotlightSizePreview;

Spotlight Border Width

spotlight-card

import SpotlightCard, { SpotlightCardsContainer }  from "@/components/interactions/spotlight-card/spotlight-card";

const SpotlightCardSpotlightBorderWidthPreview = () => {
  return (
    <SpotlightCardsContainer
      style={spotlightCardsContainerStyles}
    >
      {cards.map((card) => (
        <SpotlightCard
          spotlightColor="rgb(132, 250, 175)"
          spotlightBorderWidth={4}
          style={spotlightCardStyles}
          wrapperProps={{
            style: spotlightCardWrapperStyles,
          }}
        >
          <span style={cardIcon}>{card.icon}</span>
          <h2 style={cardHeading}>{card.title}</h2>
          <p>{card.description}</p>
        </SpotlightCard>
      ))}
    </SpotlightCardsContainer>
  );
};

const spotlightCardsContainerStyles = {
  margin: "32px",
  display: "flex",
  gap: "16px",
  flexWrap: "wrap",
  justifyContent: "center",
};

const spotlightCardStyles = {
  width: "320px",
  borderRadius: "8px",
};

const spotlightCardWrapperStyles = {
  background: "var(--layer-secondary, #111111)",
  padding: "16px",
};

const cardHeading = {
  fontSize: "1.25em",
};

const cardIcon = {
  display: "inline-block",
  marginBottom: "16px",
};

const cards = [
  {
    icon: "",
    title: "Fast Performance",
    description: "Experience lightning-fast load times and smooth interactions across all devices.",
  },
  {
    icon: "🔒",
    title: "Secure by Default",
    description: "Built with modern security practices to keep your data safe and protected.",
  },
  {
    icon: "🎨",
    title: "Customizable UI",
    description: "Easily adapt components to match your brand and design system.",
  },
  {
    icon: "📱",
    title: "Responsive Design",
    description: "Optimized layouts that look great on mobile, tablet, and desktop screens.",
  },
  {
    icon: "🧑‍💻",
    title: "Developer Friendly",
    description: "Simple APIs and flexible props make development fast and enjoyable.",
  },
  {
    icon: "📈",
    title: "Scalable Architecture",
    description: "Designed to grow with your application from small projects to large systems.",
  },
];

export default SpotlightCardSpotlightBorderWidthPreview;

Props

Configure the component with the following props:


SpotlightCardsContainer Props

PropTypeRequiredDefaultDescription
childrenReact.ReactNodeYesOne or more SpotlightCard components to be rendered inside the container.
tagNamestringNo"div"HTML tag used to render the container element.
classNamestringNoAdditional CSS classes applied to the container.
styleReact.CSSPropertiesNoInline styles applied to the container element.

SpotlightCard Props

PropTypeRequiredDefaultDescription
childrenReact.ReactNodeYesContent to be rendered inside the card.
spotlightColorstringNo"rgb(127, 127, 127)"Spotlight color. Accepts RGB or HEX; alpha channel is used to control spotlight intensity.
spotlightSizenumberNo100Size of the spotlight effect in pixels.
spotlightBorderWidthnumberNo1Width of the card border in pixels; adopts spotlight color on hover proximity.
tagNamestringNo`“div”HTML tag used to render the card container.
classNamestringNoAdditional CSS classes applied to the card.
styleReact.CSSPropertiesNoInline styles applied to the card.
wrapperTagNamestringNo"div"HTML tag used for the inner wrapper around children.
wrapperPropsRecord<string, any>No{}Props passed to the wrapper element (e.g., className, style, etc.).