Introducing Aceternity UI Pro - 70+ premium component packs and templates to build amazing websites.
Logo

3D Marquee

A 3D Marquee effect with grid, good for showcasing testimonials and hero sections

Open in
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7

Installation

Install dependencies

npm i motion clsx tailwind-merge

Add util file

lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Copy the source code

components/ui/3d-marquee.tsx

"use client";
 
import { motion } from "motion/react";
import { cn } from "@/lib/utils";
export const ThreeDMarquee = ({
  images,
  className,
}: {
  images: string[];
  className?: string;
}) => {
  // Split the images array into 4 equal parts
  const chunkSize = Math.ceil(images.length / 4);
  const chunks = Array.from({ length: 4 }, (_, colIndex) => {
    const start = colIndex * chunkSize;
    return images.slice(start, start + chunkSize);
  });
  return (
    <div
      className={cn(
        "mx-auto block h-[600px] overflow-hidden rounded-2xl max-sm:h-100",
        className,
      )}
    >
      <div className="flex size-full items-center justify-center">
        <div className="size-[1720px] shrink-0 scale-50 sm:scale-75 lg:scale-100">
          <div
            style={{
              transform: "rotateX(55deg) rotateY(0deg) rotateZ(-45deg)",
            }}
            className="relative top-96 right-[50%] grid size-full origin-top-left grid-cols-4 gap-8 transform-3d"
          >
            {chunks.map((subarray, colIndex) => (
              <motion.div
                animate={{ y: colIndex % 2 === 0 ? 100 : -100 }}
                transition={{
                  duration: colIndex % 2 === 0 ? 10 : 15,
                  repeat: Infinity,
                  repeatType: "reverse",
                }}
                key={colIndex + "marquee"}
                className="flex flex-col items-start gap-8"
              >
                <GridLineVertical className="-left-4" offset="80px" />
                {subarray.map((image, imageIndex) => (
                  <div className="relative" key={imageIndex + image}>
                    <GridLineHorizontal className="-top-4" offset="20px" />
                    <motion.img
                      whileHover={{
                        y: -10,
                      }}
                      transition={{
                        duration: 0.3,
                        ease: "easeInOut",
                      }}
                      key={imageIndex + image}
                      src={image}
                      alt={`Image ${imageIndex + 1}`}
                      className="aspect-[970/700] rounded-lg object-cover ring ring-gray-950/5 hover:shadow-2xl"
                      width={970}
                      height={700}
                    />
                  </div>
                ))}
              </motion.div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};
 
const GridLineHorizontal = ({
  className,
  offset,
}: {
  className?: string;
  offset?: string;
}) => {
  return (
    <div
      style={
        {
          "--background": "#ffffff",
          "--color": "rgba(0, 0, 0, 0.2)",
          "--height": "1px",
          "--width": "5px",
          "--fade-stop": "90%",
          "--offset": offset || "200px", //-100px if you want to keep the line inside
          "--color-dark": "rgba(255, 255, 255, 0.2)",
          maskComposite: "exclude",
        } as React.CSSProperties
      }
      className={cn(
        "absolute left-[calc(var(--offset)/2*-1)] h-[var(--height)] w-[calc(100%+var(--offset))]",
        "bg-[linear-gradient(to_right,var(--color),var(--color)_50%,transparent_0,transparent)]",
        "[background-size:var(--width)_var(--height)]",
        "[mask:linear-gradient(to_left,var(--background)_var(--fade-stop),transparent),_linear-gradient(to_right,var(--background)_var(--fade-stop),transparent),_linear-gradient(black,black)]",
        "[mask-composite:exclude]",
        "z-30",
        "dark:bg-[linear-gradient(to_right,var(--color-dark),var(--color-dark)_50%,transparent_0,transparent)]",
        className,
      )}
    ></div>
  );
};
 
const GridLineVertical = ({
  className,
  offset,
}: {
  className?: string;
  offset?: string;
}) => {
  return (
    <div
      style={
        {
          "--background": "#ffffff",
          "--color": "rgba(0, 0, 0, 0.2)",
          "--height": "5px",
          "--width": "1px",
          "--fade-stop": "90%",
          "--offset": offset || "150px", //-100px if you want to keep the line inside
          "--color-dark": "rgba(255, 255, 255, 0.2)",
          maskComposite: "exclude",
        } as React.CSSProperties
      }
      className={cn(
        "absolute top-[calc(var(--offset)/2*-1)] h-[calc(100%+var(--offset))] w-[var(--width)]",
        "bg-[linear-gradient(to_bottom,var(--color),var(--color)_50%,transparent_0,transparent)]",
        "[background-size:var(--width)_var(--height)]",
        "[mask:linear-gradient(to_top,var(--background)_var(--fade-stop),transparent),_linear-gradient(to_bottom,var(--background)_var(--fade-stop),transparent),_linear-gradient(black,black)]",
        "[mask-composite:exclude]",
        "z-30",
        "dark:bg-[linear-gradient(to_bottom,var(--color-dark),var(--color-dark)_50%,transparent_0,transparent)]",
        className,
      )}
    ></div>
  );
};

Examples

Standard

Open in
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7

Full screen with centered text

Open in

This is your life and it's ending one moment at a time.

You are not your job, you're not how much money you have in the bank. You are not the car you drive. You're not the contents of your wallet.

Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7
Image 8
Image 1
Image 2
Image 3
Image 4
Image 5
Image 6
Image 7

Props

ThreeDMarquee Component Props

PropTypeRequiredDefaultDescription
imagesstring[]Yes-Array of image URLs to display in the 3D marquee. The component automatically splits these images into 4 columns.
classNamestringNoundefinedAdditional CSS classes to apply to the 3D marquee container.

GridLineHorizontal Component Props

PropTypeRequiredDefaultDescription
classNamestringNoundefinedAdditional CSS classes to apply to the horizontal grid line.
offsetstringNo"200px"Controls the extension of the line beyond the element's boundaries.

GridLineVertical Component Props

PropTypeRequiredDefaultDescription
classNamestringNoundefinedAdditional CSS classes to apply to the vertical grid line.
offsetstringNo"150px"Controls the extension of the line beyond the element's boundaries.

Build websites faster and 10x better than your competitors with Aceternity UI Pro

With the best in class components and templates, stand out from the crowd and get more attention to your website. Trusted by founders and entrepreneurs from all over the world.

Go Pro
The work that Manu did laid the foundation of online education that we provide today. The website he built for us is used by thousands of students every day. He took the requirements and built the ...

Jagvinder Kour

Chairperson at Golden Bells Academy

A product by Aceternity
Building in public at @mannupaaji