Marquee React component for Tailwind CSS v4
v1.0.0Added in 1.10.0
An infinite scroller for logos, cards, tags or any content, horizontal or vertical, with edge fades and a pause control.
The content glides in a seamless loop at a steady pace in pixels per second, so long and short strips move at the same speed, and the ends fade into the background. Hover the strip, tab into a link inside it or rest a finger on it and it holds still until you let go. With showControl a round pause button sits next to the track; it is a toggle with aria-pressed, reachable with Tab and switched with Enter or Space. Screen readers read the content once, because the moving copies are hidden from them and from the keyboard. Copies are added on their own until the viewport is always covered, it stops painting while scrolled out of view, and under prefers-reduced-motion it turns into a still, wrapped row (or a clipped column). Right-to-left pages are supported.
Marquee is a Pro React component in the React effect components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add marquee, together with Motion presets, UIProvider and useControllableState and the npm packages lucide-react and motion.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add marqueePro item: the CLI needs a Pro account. See plans
Usage
Import Marquee from @/components/ui/marquee.
import Marquee, { MarqueeItem } from "@/components/ui/marquee"
const tags = ["React", "Next.js", "Tailwind CSS", "TypeScript", "Motion", "Radix UI"]
export function TagStrip() {
return (
<Marquee speed={30} showControl>
{tags.map((tag) => (
<MarqueeItem key={tag} className="rounded-full bg-foreground/5 px-3 py-1.5 text-sm">
{tag}
</MarqueeItem>
))}
</Marquee>
)
}Props
childrenReactNodeThe items, laid out in a row (or a column for up and down). Wrap each one in MarqueeItem, or pass any elements.
directiondefault"left""left" | "right" | "up" | "down"Which way the content travels. Up and down make a vertical column that needs a height (h-80 by default).
speeddefault40numberPixels per second. The loop duration comes from the measured content length, so the pace is the same for any content.
gapdefault"md""sm" | "md" | "lg"Space between items and between the repeated copies: 16 / 24 / 40px, one step smaller below 640px.
repeatdefault2numberThe fewest copies of the content on the track. More copies are added on their own until the viewport is always covered.
pauseOnHoverdefaulttruebooleanHolds still while the strip is hovered, while focus is inside it, and while a finger rests on it on touch screens. The pause control does not count, so play resumes right away.
fadedefaulttruebooleanFades both ends of the strip into whatever sits behind it, with a CSS mask.
fadeSizedefault"md""sm" | "md" | "lg"How far the fade reaches: about 5%, 10% or 18% of the strip, with a floor and a ceiling in pixels.
reversedefaultfalsebooleanFlips the direction. Handy for stacked rows that should move against each other.
pausedbooleanControlled pause state; pair it with onPausedChange. Leave it undefined to let the control (and defaultPaused) own it.
defaultPauseddefaultfalsebooleanStarts paused while uncontrolled. Pair it with showControl so people can start it.
onPausedChange(paused: boolean) => voidFires when the pause control is toggled.
showControldefaultfalsebooleanShows a small pause button next to the track, so people can stop motion that runs for long (WCAG 2.2.2). Hidden under reduced motion, where nothing moves.
pauseLabeldefault"Pause"stringThe control's accessible name. It stays the same in both states; aria-pressed tells whether it is paused.
playLabeldefault"Play"stringThe control's tooltip while paused.
classNamesPartial<Record<"viewport" | "track" | "group" | "item" | "control", string>>Extra classes for the clipping viewport, the moving track, each copy of the content, every MarqueeItem and the pause button.
classNamestringExtra classes for the root, merged last. Set the height of a vertical marquee here, e.g. h-[420px].
Parts
MarqueeItem
componentAn optional wrapper for one item. It never shrinks and picks up classNames.item from the marquee. Takes div props.
MarqueeDirection
typeThe direction union: "left" | "right" | "up" | "down".
MarqueeProps
typeThe props of Marquee: div props plus the options above.
MarqueeItemProps
typeThe props of MarqueeItem, the same as a div.
MarqueeGap
typeThe gap union: "sm" | "md" | "lg".
MarqueeFadeSize
typeThe fade size union: "sm" | "md" | "lg".
MarqueeSlot
typeThe keys of classNames.
Examples
Logo strip
The classic customer row, with wide gaps and soft edges.
Rows in opposite directions
Two rows of cards, the second with reverse, so they slide past each other.
Vertical feed
direction up in a fixed height, fading at the top and bottom.
Tag cloud
Three rows of chips at different speeds.
Announcement bar with a pause button
showControl adds a toggle with aria-pressed, for motion that runs for long.
Files
- components/ui/marquee.tsx
FAQ
Is the React Marquee component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Marquee?
Run npx wingo-ui@latest add marquee. It also adds the Wingo UI items it builds on (Motion presets, UIProvider and useControllableState) and the npm packages lucide-react and motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/marquee.