my/ui

Command Palette

Search for a command to run...

All components

Marquee

effects

An infinite, seamless marquee with hover-pause and vertical support.

responsive · 320px

Install

Same command in any shadcn project — React (Vite/CRA), Next.js, Remix, Astro, and more:

$npx shadcn@latest add https://your-domain/r/marquee.json

Usage

import { Marquee } from "@/registry/effects/marquee";

const items = ["Next.js", "TypeScript", "Tailwind", "shadcn/ui", "Radix", "Motion"];

export default function MarqueeDemo() {
  return (
    <div className="relative w-full max-w-md overflow-hidden">
      <Marquee pauseOnHover className="[--duration:18s]">
        {items.map((item) => (
          <span key={item} className="rounded-full border border-border bg-card px-4 py-1.5 text-sm font-medium text-card-foreground">
            {item}
          </span>
        ))}
      </Marquee>
      <div className="pointer-events-none absolute inset-y-0 left-0 w-12 bg-gradient-to-r from-background" />
      <div className="pointer-events-none absolute inset-y-0 right-0 w-12 bg-gradient-to-l from-background" />
    </div>
  );
}

Component source

"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

export interface MarqueeProps extends React.ComponentPropsWithoutRef<"div"> {
  /** Scroll vertically instead of horizontally. */
  vertical?: boolean;
  /** Reverse the scroll direction. */
  reverse?: boolean;
  /** Pause the animation on hover. */
  pauseOnHover?: boolean;
  /** Number of times to repeat the children for a seamless loop. */
  repeat?: number;
}

/**
 * An infinite, seamless marquee. Wrap any set of children; they scroll
 * forever. Set `--duration` and `--gap` via style to tune speed/spacing.
 */
export const Marquee = React.forwardRef<HTMLDivElement, MarqueeProps>(
  (
    {
      className,
      vertical = false,
      reverse = false,
      pauseOnHover = false,
      repeat = 4,
      children,
      style,
      ...props
    },
    ref,
  ) => {
    return (
      <div
        ref={ref}
        style={
          {
            "--duration": "40s",
            "--gap": "1rem",
            ...style,
          } as React.CSSProperties
        }
        className={cn(
          "group flex overflow-hidden p-2 [gap:var(--gap)]",
          vertical ? "flex-col" : "flex-row",
          className,
        )}
        {...props}
      >
        {Array.from({ length: repeat }).map((_, i) => (
          <div
            key={i}
            className={cn("flex shrink-0 justify-around [gap:var(--gap)]", {
              "animate-marquee flex-row": !vertical,
              "animate-marquee-vertical flex-col": vertical,
              "group-hover:[animation-play-state:paused]": pauseOnHover,
              "[animation-direction:reverse]": reverse,
            })}
          >
            {children}
          </div>
        ))}
      </div>
    );
  },
);

Marquee.displayName = "Marquee";