Marquee Ticker
An infinite scrolling strip of words or logos that pauses when you hover it.
- Category
- Components
- Added
- 2026-08-21
- Deps
- none
- Updated
- 2026-09-01
Props
"use client";
const ITEMS = ["Design", "Ship", "Iterate", "Repeat", "Vibe Code"];
export function MarqueeTicker({
items = ITEMS.join(", "),
speed = 24,
direction = "left",
pauseOnHover = true,
}: {
/** Comma-separated. */
items?: string;
speed?: number;
direction?: "left" | "right";
pauseOnHover?: boolean;
}) {
const words = items
.split(",")
.map((w) => w.trim())
.filter(Boolean);
const track = [...words, ...words];
return (
<div className="group relative w-full overflow-hidden py-3">
<div className="pointer-events-none absolute inset-y-0 left-0 z-10 w-10 bg-gradient-to-r from-white to-transparent" />
<div className="pointer-events-none absolute inset-y-0 right-0 z-10 w-10 bg-gradient-to-l from-white to-transparent" />
<div
className={
"flex w-max gap-8 " +
(pauseOnHover ? "group-hover:[animation-play-state:paused]" : "")
}
style={{
animation: `marquee ${speed}s linear infinite`,
animationDirection: direction === "right" ? "reverse" : "normal",
}}
>
{track.map((item, i) => (
<span
key={`${item}-${i}`}
className="flex items-center gap-8 text-2xl font-bold tracking-tight whitespace-nowrap"
>
{item}
<span className="text-red-500">•</span>
</span>
))}
</div>
<style jsx>{`
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
`}</style>
</div>
);
}
About this component
An infinite ticker needs a loop where the seam never shows, and this fakes it with one trick: the item array is duplicated exactly once, and the doubled track animates translateX from 0 to -50%, landing precisely where the second copy begins so it can snap back to 0 with no visible jump — as long as the two halves stay pixel-identical in width. That's the fragile part: async-loaded content with no fixed dimensions between the two copies would make the seam visible as a stutter. It's pure CSS, no requestAnimationFrame or interval driving it, so it costs nothing per frame regardless of what else is re-rendering on the page; reach for a JS-driven approach only if the scroll speed needs to react to something dynamic, like page scroll position, which a keyframe can't. Pause-on-hover is one Tailwind arbitrary-value class, no event handlers. The direction prop doesn't define a second, mirrored keyframe for "right" — it just replays the same keyframe with animationDirection: reverse, a subtler and cheaper way to get the same result.
Curator’s note
Pure CSS animation, not JS-driven — stays smooth at 60fps even with a dozen items and zero re-renders.
Pairs well with
Matched on shared tags and category — the entries most likely to be used alongside this one.
Ticker Tape Background
backgroundsOversized, near-invisible ticker rows scrolling behind the fold as ambient texture.
Velocity Marquee Rail
animationsA marquee that accelerates with scroll speed and flips direction when you scroll back.
Chapter Progress Nav
componentsA sticky section nav whose items fill like progress bars as you read — and still reaches the short last section that fixed-line navs never activate.