Seamless looping ticker marquee supporting configurable direction, speed, gap spacing, pause-on-hover, and aria-hidden track duplication without layout jumps.
Preview
Customize
4 props22
14
Installation
Copies the component source and its CSS Module into your project.
npx motionfoundry add infinite-marqueeWrites components/ui/infinite-marquee.tsx and components/styles/infinite-marquee.module.css.
Usage
Updates live as you change the controls — only props that differ from the defaults are included.
example.tsx
1import { InfiniteMarquee } from "@motionfoundry/ui";2 3export function Example() {4 return (5 <InfiniteMarquee />6 );7}Props
Accessibility
Marks the duplicated second track aria-hidden='true' so screen readers only read the item list once, and pauses translation automatically on hover, focus-within, or prefers-reduced-motion.
Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.
Source
components/ui/infinite-marquee.tsx
1"use client";2 3import * as React from "react";4import styles from "../styles/infinite-marquee.module.css";5 6export interface InfiniteMarqueeProps {7 /** String items to display in the marquee */8 items?: string[];9 /** Scroll direction */10 direction?: "left" | "right";11 /** Loop cycle duration in seconds */12 speed?: number;13 /** Gap between items in pixels */14 gap?: number;15 /** Pause animation on pointer hover */16 pauseOnHover?: boolean;17 /** Optional className */18 className?: string;19}20 21const DEFAULT_ITEMS = [22 "KineticType",23 "GlyphCascade",24 "LiquidGlassCard",25 "HoloTiltCard",26 "MagneticDock",27 "MorphingCommandPalette",28];29 30export function InfiniteMarquee({31 items = DEFAULT_ITEMS,32 direction = "left",33 speed = 22,34 gap = 14,35 pauseOnHover = true,36 className = "",37}: InfiniteMarqueeProps) {38 const safeItems = items.length > 0 ? items : DEFAULT_ITEMS;39 const styleVars = {40 "--mf-mq-speed": `${speed}s`,41 "--mf-mq-gap": `${gap}px`,42 "--mf-mq-dir": direction === "right" ? "reverse" : "normal",43 } as React.CSSProperties;44 45 return (46 <div47 style={styleVars}48 className={`${styles.viewport} ${pauseOnHover ? styles.pauseOnHover : ""} ${className}`.trim()}49 >50 <div className={styles.track}>51 {safeItems.map((item, idx) => (52 <span key={`primary-${idx}`} className={styles.pill}>53 {item}54 </span>55 ))}56 </div>57 <div aria-hidden="true" className={styles.track}>58 {safeItems.map((item, idx) => (59 <span key={`clone-${idx}`} className={styles.pill}>60 {item}61 </span>62 ))}63 </div>64 </div>65 );66}