Marquee
A seamless infinite scroller for logos, testimonials, or any row of content - GPU-cheap, pausing on hover, with faded edges.
The content is rendered twice and the track translates by exactly -50%, so the
loop is perfectly continuous. Only transform animates, off the main thread, so
it stays smooth. It pauses on hover and stops under reduced motion.
Installation
Complete the shared Setup first, then copy the component into
components/ui/marquee.tsx.
'use client';import * as React from 'react';import { cn } from '@/lib/cn';export type MarqueeProps = { children: React.ReactNode; duration?: number; direction?: 'left' | 'right'; gap?: number; pauseOnHover?: boolean; fade?: boolean; className?: string;};export function Marquee({ children, duration = 30, direction = 'left', gap = 40, pauseOnHover = true, fade = true, className,}: MarqueeProps) { const mask = fade ? 'linear-gradient(to right, transparent, #000 10%, #000 90%, transparent)' : undefined; return ( <div data-pause={pauseOnHover ? 'true' : 'false'} className={cn('marquee group relative w-full overflow-hidden', className)} style={{ maskImage: mask, WebkitMaskImage: mask }} > <div className="marquee-track flex w-max flex-nowrap" data-direction={direction} style={{ gap, ['--marquee-duration' as string]: `${duration}s` }} > <div className="flex shrink-0 items-center" style={{ gap }}> {children} </div> <div className="flex shrink-0 items-center" style={{ gap }} aria-hidden> {children} </div> </div> </div> );}Add the animation to your global.css.
@keyframes marquee-x {
to {
transform: translateX(-50%);
}
}
@keyframes marquee-x-reverse {
to {
transform: translateX(50%);
}
}
.marquee-track {
animation: marquee-x var(--marquee-duration, 30s) linear infinite;
}
.marquee-track[data-direction='right'] {
animation-name: marquee-x-reverse;
}
.marquee[data-pause='true']:hover .marquee-track {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.marquee-track {
animation: none !important;
}
}Usage
import { Marquee } from '@/components/ui/marquee';
export default function Example() {
return (
<Marquee>
<span>Next.js</span>
<span>React</span>
<span>TypeScript</span>
</Marquee>
);
}Examples
Two rows scrolling in opposite directions.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | The content to scroll. |
duration | number | 30 | Seconds for one full loop. |
direction | "left" | "right" | "left" | Scroll direction. |
gap | number | 40 | Gap between items, in px. |
pauseOnHover | boolean | true | Pause while hovered. |
fade | boolean | true | Fade the left and right edges. |
className | string | - | Forwarded to the root. |