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.
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 UtilityPreview
Switch between light and dark to inspect the embedded Storybook preview.
Installation
pnpm dlx shadcn@latest add https://ui.vllnt.com/r/marquee.jsonStorybook
Explore all variants, controls, and accessibility checks in the interactive Storybook playground.
View in Storybook5 stories available:
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";
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.