Marquee

Scroll logos, badges, or testimonials in a seamless infinite loop in a React app. Pause-on-hover, vertical mode, and speed presets. Install via the shadcn CLI.

Report a bug

What it is & when to use

Marquee is a utility that scrolls its children in a continuous, seamless loop — ideal for logo walls, badge rows, testimonials, or status chips. It duplicates the track so the animation never shows a gap, and supports horizontal or vertical direction, reverse, an adjustable gap, a repeat count, and three speed presets (slow, normal, fast) or an explicit duration. Edge fade and pause-on-hover are optional, and it respects prefers-reduced-motion by halting the animation.

Browse all Utility

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.com/r/marquee.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

import * as React from "react"; import { cn } from "../../lib/utils"; export type MarqueeSpeed = "fast" | "normal" | "slow"; export type MarqueeProps = React.ComponentPropsWithoutRef<"div"> & { duration?: number; fade?: boolean; gap?: number | string; pauseOnHover?: boolean; repeat?: number; reverse?: boolean; speed?: MarqueeSpeed; vertical?: boolean; }; function getGapValue(gap: number | string): string { return typeof gap === "number" ? `${gap}px` : gap; } function getMaskImage(vertical: boolean): string { return vertical ? "linear-gradient(to bottom, transparent, black 12%, black 88%, transparent)" : "linear-gradient(to right, transparent, black 12%, black 88%, transparent)"; } function getTrackItems( children: React.ReactNode, repeat: number, ): React.ReactNode[] { const items = React.Children.toArray(children); return Array.from({ length: Math.max(1, repeat) }, (_, copyIndex) => items.map((item, itemIndex) => ( <div className="shrink-0" key={`${copyIndex}-${itemIndex}`}> {item} </div> )), ).flat(); } function getDuration( duration: number | undefined, speed: MarqueeSpeed, ): number { if (duration !== undefined) { return duration; } switch (speed) { case "fast": return 10; case "normal": return 20; case "slow": return 32; } } export const Marquee = ({ children, className, duration, fade = true, gap = "1rem", pauseOnHover = false, ref, repeat = 1, reverse = false, speed = "normal", style, vertical = false, ...props }: MarqueeProps & { ref?: React.Ref<HTMLDivElement> }) => { const resolvedGap = getGapValue(gap); const resolvedDuration = getDuration(duration, speed); const trackItems = getTrackItems(children, repeat); const animationStyle: React.CSSProperties = { animationDirection: reverse ? "reverse" : "normal", animationDuration: `${resolvedDuration}s`, animationIterationCount: "infinite", animationName: vertical ? "vllnt-marquee-y" : "vllnt-marquee-x", animationTimingFunction: "linear", }; const maskImage = getMaskImage(vertical); return ( <div className={cn( "group relative overflow-hidden", vertical ? "flex h-full flex-col" : "flex w-full flex-row", className, )} ref={ref} style={fade ? { ...style, maskImage, WebkitMaskImage: maskImage } : style} {...props} > <div className={cn( "flex shrink-0 will-change-transform motion-reduce:animate-none motion-reduce:transform-none", pauseOnHover && "group-hover:[animation-play-state:paused]", vertical ? "min-h-full flex-col" : "min-w-full flex-row", )} style={animationStyle} > {[0, 1].map((groupIndex) => ( <div aria-hidden={groupIndex === 1} className={cn( "flex shrink-0", vertical ? "flex-col items-stretch" : "flex-row items-center", )} key={groupIndex} style={{ gap: resolvedGap }} > {trackItems} </div> ))} </div> </div> ); }; Marquee.displayName = "Marquee";

Dependencies

  • @vllnt/ui@^0.3.0

Frequently asked questions

What is Marquee?
Marquee is a React component that scrolls its children in an infinite, seamless loop. It is commonly used for logo clouds, badge rows, and scrolling testimonials or status chips.
How do I add Marquee to my app?
Run `pnpm dlx shadcn@latest add https://ui.vllnt.com/r/marquee.json`, then wrap your items: `<Marquee pauseOnHover speed="slow">…</Marquee>`.
Can Marquee pause on hover and respect reduced motion?
Yes. Set `pauseOnHover` to stop the scroll while hovered, and Marquee honors `prefers-reduced-motion` by disabling the animation. You can also set `vertical` or `reverse` for direction, and `speed` or `duration` for pace.