Ghost Cubes

Translucent cubes drift upward and fade away, creating a subtle ghost-like background motion.

1. Copy the component file

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

ghost-cubes-background.jsx

import { useCallback, useEffect, useState } from "react";
import styles from "./ghost-cubes-background.module.css";

const Cube = (props) => {
  const {
    index,
  } = props;

  const getCube = useCallback(() => ({
    id: `cube-${Math.floor(Math.random() * 10000)}`,
    xPos: Math.random() * 100, // position in % of width
    size: Math.random() * 100, // size in px
    durationMs: 10000 + Math.random() * 20000,
    delayMs: Math.random() * 5000 * index,
  }), [index]);

  const [cube, setCube] = useState(getCube);

  useEffect(() => {
    const timeoutId = setTimeout(() => {
      setCube(getCube());
    }, cube.durationMs + cube.delayMs + 1000);
    return () => {
      clearTimeout(timeoutId);
    };
  }, [cube, getCube]);

  return (
    <span
      key={cube.id}
      aria-hidden={true}
      className={styles["cube"]}
      style={{
        "--size": `${cube.size}px`,
        "--xPos": `${cube.xPos}%`,
        "--durationMs": `${cube.durationMs}ms`,
        "--delayMs": `${cube.delayMs}ms`,
      }}
    />
  );
};

const GhostCubesBackground = (props) => {
  const {
    children,
    className = "",
    style = null,
    wrapperProps = {},
    wrapperTagName = "div",
    backgroundColor = "#d37185",
    ...rest
  } = props;

  const cubesCount = 20;

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

  const Wrapper = wrapperTagName || "div";

  return (
    <div 
      {...rest}
      className={[
        className,
        styles["ghost-cube-background"]
      ].join(" ")}
      style={{
        ...style,
        "--background-color": backgroundColor,
      }}
    >
      <div 
        aria-hidden={true}
        className={styles["cubes"]}
      >
        {new Array(cubesCount).fill(0).map((_, index) => (
          <Cube
            key={`cube-${index}`}
            index={index}
          />
        ))}
      </div>
      <Wrapper
        {...restWrapperProps} 
        className={[
          wrapperClassName,
          styles["wrapper"]
        ].join(" ")}
      >
        {children}
      </Wrapper>
    </div>
  );
};

export default GhostCubesBackground;

2. Copy the CSS module file

In the same folder, create a file called ghost-cubes-background.module.css and paste the following CSS.

ghost-cubes-background.module.css

.ghost-cube-background {
  --cube-background-color: rgba(255, 255, 255, 0.25);

  @media(prefers-color-scheme: dark) {
    --cube-background-color: rgba(0, 0, 0, 0.25);
  }

  position: relative;
  background: var(--background-color);

  .cubes {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
    pointer-events: none;

    .cube {
      position: absolute;
      left: var(--xPos);
      bottom: calc(var(--size) * -1.5);
      opacity: 0.5;
      width: var(--size);
      height: var(--size);
      transform:rotate(45deg);
      background: rgba(255, 255, 255, 0.25);
      z-index: 1;
      pointer-events: none;
      animation: ghost-cube-keyframes var(--durationMs) ease-in forwards;
      animation-delay: var(--delayMs);
    }
  }

  .wrapper {
    position: relative;
    z-index: 3;
  }
}

@keyframes ghost-cube-keyframes {
  0% {
    transform: translateY(0px) rotate(45deg);
    opacity: 0.5;
    border-radius: 5%;
  }
  50% {
    border-radius: 45%;
  } 
  100% {
    transform: translateY(calc(-100vh - var(--size))) rotate(900deg);
    opacity: 0;
    border-radius: 45%;
  }
}

3. Use the component

Now you can import and use the component anywhere in your project.

ghost-cubes-background-preview.jsx

import GhostCubesBackground from "@/components/backgrounds/ghost-cubes-background/ghost-cubes-background.jsx";

const GhostCubesBackgroundPreview = () => {
  return (
    <GhostCubesBackground
      style={{
        width: "100%",
        height: "100%",
      }}
      wrapperProps = {{
        style: {
          display: "grid",
          placeItems: "center",
          height: "100%"
        }
      }}
    >
      <h1>Ghost Cubes</h1>
    </GhostCubesBackground>
  );
};

export default GhostCubesBackgroundPreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
childrenReactNodeYesContent displayed on top of the ghost cubes background.
classNamestringNoAdditional CSS classes applied to the main container.
styleReact.CSSPropertiesNoInline styles applied to the main container.
wrapperPropsReact.HTMLAttributes<any>NoAdditional props passed to the wrapper element containing the children.
wrapperTagNamestringNo"div"HTML tag used as the wrapper element around the children.
backgroundColorstringNo"#d37185"Background color behind the floating ghost cubes animation.