shadimage

Marquee

A marquee component that infinitely scrolls content in a horizontal or vertical direction.

registry/components/marquee.tsx

import { type ComponentPropsWithoutRef } from "react"

import { cn } from "@/lib/utils"

interface MarqueeProps extends ComponentPropsWithoutRef<"div"> {
  /**
   * Optional CSS class name to apply custom styles
   */
  className?: string
  /**
   * Whether to reverse the animation direction
   * @default false
   */
  reverse?: boolean
  /**
   * Whether to pause the animation on hover
   * @default false
   */
  pauseOnHover?: boolean
  /**
   * Content to be displayed in the marquee
   */
  children: React.ReactNode
  /**
   * Whether to animate vertically instead of horizontally
   * @default false
   */
  vertical?: boolean
  /**
   * Number of times to repeat the content
   * @default 4
   */
  repeat?: number
}

export function Marquee({
  className,
  reverse = false,
  pauseOnHover = false,
  children,
  vertical = false,
  repeat = 4,
  style,
  ...props
}: MarqueeProps) {
  // Edge-fade mask applied via CSS mask-image so content fades out at both ends.
  const fadeMask = vertical
    ? "linear-gradient(to bottom, transparent 0%, black 12%, black 88%, transparent 100%)"
    : "linear-gradient(to right, transparent 0%, black 12%, black 88%, transparent 100%)"

  return (
    <div
      {...props}
      style={{
        ...style,
        maskImage: fadeMask,
        WebkitMaskImage: fadeMask,
      }}
      className={cn(
        "group relative flex gap-(--gap) overflow-hidden [--duration:40s] [--gap:12px]",
        vertical ? "flex-col" : "flex-row",
        className
      )}
    >
      {/* Duplicate children N times so the marquee loops seamlessly without a visible gap. */}
      {Array.from({ length: repeat }).map((_, i) => (
        <div
          key={i}
          className={cn(
            "flex shrink-0 justify-around gap-(--gap) motion-reduce:animate-none",
            vertical
              ? "animate-marquee-vertical flex-col"
              : "animate-marquee flex-row",
            reverse && "direction-[reverse]",
            pauseOnHover && "group-hover:paused"
          )}
        >
          {children}
        </div>
      ))}
    </div>
  )
}
Interaction
marquee