Something new is coming.Join the waitlist

Infinite Marquee

PreviousNext

React infinite scrolling marquee for logo walls, testimonials, or trust badges. CSS-only animation, pause-on-hover, edge fade mask, vertical and reverse variants.

Acme
Vercel
Linear
Supabase
Stripe
Notion
Figma
Anthropic

Installation

npx shadcn@latest add https://tentui.com/r/marquee.json

Usage

Wrap a row of children. The component duplicates the row in place so the animation loops without a visible seam.

import { Marquee } from "@/components/marquee";
 
const logos = ["Acme", "Vercel", "Linear", "Supabase", "Stripe"];
 
export function LogoWall() {
  return (
    <Marquee>
      {logos.map((name) => (
        <span key={name} className="text-sm font-semibold">
          {name}
        </span>
      ))}
    </Marquee>
  );
}

The animation is plain CSS @keyframes — no JavaScript animation loop and no Intersection Observer. The track is duplicated repeat times and translated by (-100% - gap); with at least two copies the loop is seamless.

Patterns

Two opposing rows

Stack two marquees with reverse toggled to get a richer visual without doubling the runtime cost.

<Marquee duration={30}>{children}</Marquee>
<Marquee duration={30} reverse>{children}</Marquee>

Vertical orientation

Useful for sidebar testimonials. Wrap in a fixed-height container.

<div className="h-80">
  <Marquee vertical duration={50}>{quotes}</Marquee>
</div>

Pause on focus

pauseOnHover covers most cases. To pause when any child is keyboard-focused, add focus-within:[animation-play-state:paused] via className on a wrapper, or extend the component.

Props

PropTypeDefaultDescription
reversebooleanfalseReverse the scroll direction.
pauseOnHoverbooleantruePause the animation while the marquee is hovered.
verticalbooleanfalseScroll vertically — wrap in a fixed-height container.
repeatnumber4Number of times the children are duplicated. Two is the minimum for a seamless loop; more gives you a denser fill on wide viewports.
durationnumber40Animation duration in seconds. Lower = faster scroll.
fadebooleantrueApply a fade-out CSS mask at the leading and trailing edges.
classNamestringForwarded to the outer wrapper.

Accessibility & motion

  • Only the first track is in the accessibility tree — the duplicates have aria-hidden="true" so screen readers don't read the same logos N times.
  • A prefers-reduced-motion: reduce media query disables the animation entirely. Users who opt out of motion still see the content, just stationary.

Performance

The track is one transform animation per copy — GPU-accelerated and cheap. The CSS mask gradient is also free on the GPU. The biggest cost is rendering the children themselves, so keep them lightweight (no heavy shadows or filters per item) on long lists.