Customize
Usage
import { Marquee } from "@/components/motionkit/marquee";
<Marquee>
{logos.map((logo) => (
<span key={logo} className="mx-4">{logo}</span>
))}
</Marquee>Motion
The exact curves this component runs, plotted with Motion's own maths.
Scroll
Runs on its own
Works in the App Router as is. Requires Tailwind CSS v4.
npm install motionSave as components/motionkit/marquee.tsx.
"use client";
import { useRef, useState, type ReactNode } from "react";
import { motion, useAnimationFrame, useMotionValue, useReducedMotion } from "motion/react";
type MarqueeProps = {
children: ReactNode;
/** Pixels per second. */
speed?: number;
direction?: "left" | "right";
/** Stop while hovered or while something inside has keyboard focus. */
pauseOnHover?: boolean;
/** Fade the left and right edges. */
fade?: boolean;
className?: string;
};
export function Marquee({
children,
speed = 50,
direction = "left",
pauseOnHover = true,
fade = true,
className = "",
}: MarqueeProps) {
const track = useRef<HTMLDivElement>(null);
const [paused, setPaused] = useState(false);
const reduceMotion = useReducedMotion();
const x = useMotionValue(0);
useAnimationFrame((_, delta) => {
if (reduceMotion || (pauseOnHover && paused) || !track.current) return;
// the track holds two copies, so wrapping at half its width loops seamlessly
const half = track.current.scrollWidth / 2;
const step = (speed * delta) / 1000;
let next = x.get() + (direction === "left" ? -step : step);
if (next <= -half) next += half;
if (next > 0) next -= half;
x.set(next);
});
return (
<div
// keyboard users get the same pause when focus moves into a link or button inside
onPointerEnter={() => setPaused(true)}
onPointerLeave={() => setPaused(false)}
onFocus={() => setPaused(true)}
onBlur={() => setPaused(false)}
className={`overflow-hidden ${className}`}
style={
fade
? { maskImage: "linear-gradient(to right, transparent, black 12%, black 88%, transparent)" }
: undefined
}
>
<motion.div ref={track} style={{ x }} className="flex w-max">
<div className="flex shrink-0">{children}</div>
<div className="flex shrink-0" aria-hidden>
{children}
</div>
</motion.div>
</div>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
| speed | number | 50 | Pixels per second. |
| direction | "left" | "right" | "left" | Scroll direction. |
| pauseOnHover | boolean | true | Stop while hovered or while something inside has keyboard focus. |
| fade | boolean | true | Fade the left and right edges. |
Parallax Stack
Overlapping images that drift at different speeds and tidy into a stack as you scroll, for editorial storytelling.
Orbit
Icons, labels and shapes circling a central logo on rings, each with its own speed and direction.
Accordion
FAQ panels that open with a smooth, springy height and a turning chevron.