Marquee Effect
Add dynamic movement to your UI with Marquee effect, ideal for highlighting important information with smooth, continuous scrolling. Built with Tailwind CSS and Motion.














import { MarqueeEffect } from "@/components/core/marquee-effect";
const images = Array.from({ length: 7 }, (_, i) =>
`https://bundui-images.netlify.app/products/0${i + 1}.jpeg`
);
export default function MarqueeEffectExample() {
return (
<div className="grow">
<MarqueeEffect gap={24}>
{images.map((src, i) => (
<img
key={i}
src={src}
alt={`Product ${i + 1}`}
className="w-32 aspect-square rounded-md"
/>
))}
</MarqueeEffect>
</div>
);
}
Installation
Install the following dependencies:
npm install motion clsx tailwind-merge react-use-measure
Copy and paste the following code into your project:
"use client";
import React from "react";
import { cn } from "@/lib/utils";
import { useMotionValue, animate, motion } from "motion/react";
import useMeasure from "react-use-measure";
export type InfiniteSliderProps = {
children: React.ReactNode;
gap?: number;
speed?: number;
speedOnHover?: number;
direction?: "horizontal" | "vertical";
reverse?: boolean;
className?: string;
};
export function MarqueeEffect({
children,
gap = 16,
speed = 100,
speedOnHover,
direction = "horizontal",
reverse = false,
className,
}: InfiniteSliderProps) {
const [currentSpeed, setCurrentSpeed] = React.useState(speed);
const [ref, { width, height }] = useMeasure();
const translation = useMotionValue(0);
const [isTransitioning, setIsTransitioning] = React.useState(false);
const [key, setKey] = React.useState(0);
React.useEffect(() => {
let controls;
const size = direction === "horizontal" ? width : height;
const contentSize = size + gap;
const from = reverse ? -contentSize / 2 : 0;
const to = reverse ? 0 : -contentSize / 2;
const distanceToTravel = Math.abs(to - from);
const duration = distanceToTravel / currentSpeed;
if (isTransitioning) {
const remainingDistance = Math.abs(translation.get() - to);
const transitionDuration = remainingDistance / currentSpeed;
controls = animate(translation, [translation.get(), to], {
ease: "linear",
duration: transitionDuration,
onComplete: () => {
setIsTransitioning(false);
setKey((prevKey) => prevKey + 1);
},
});
} else {
controls = animate(translation, [from, to], {
ease: "linear",
duration: duration,
repeat: Infinity,
repeatType: "loop",
repeatDelay: 0,
onRepeat: () => {
translation.set(from);
},
});
}
return controls?.stop;
}, [
key,
translation,
currentSpeed,
width,
height,
gap,
isTransitioning,
direction,
reverse,
]);
const hoverProps = speedOnHover
? {
onHoverStart: () => {
setIsTransitioning(true);
setCurrentSpeed(speedOnHover);
},
onHoverEnd: () => {
setIsTransitioning(true);
setCurrentSpeed(speed);
},
}
: {};
return (
<div className={cn("overflow-hidden", className)}>
<motion.div
className="flex w-max"
style={{
...(direction === "horizontal"
? { x: translation }
: { y: translation }),
gap: `${gap}px`,
flexDirection: direction === "horizontal" ? "row" : "column",
}}
ref={ref}
{...hoverProps}
>
{children}
{children}
</motion.div>
</div>
);
}
Update the import paths to match your project setup.
Usage
import { MarqueeEffect } from "@/components/core/marquee-effect";
const images = Array.from({ length: 7 }, (_, i) =>
`https://bundui-images.netlify.app/products/0${i + 1}.jpeg`
);
export default function MarqueeEffectExample() {
return (
<div className="grow">
<MarqueeEffect gap={24}>
{images.map((src, i) => (
<img
key={i}
src={src}
alt={`Product ${i + 1}`}
className="w-32 aspect-square rounded-md"
/>
))}
</MarqueeEffect>
</div>
);
}
Examples
Reverse














import { MarqueeEffect } from "@/components/core/marquee-effect";
const images = Array.from({ length: 7 }, (_, i) =>
`https://bundui-images.netlify.app/products/0${i + 1}.jpeg`
);
export default function MarqueeEffectExample() {
return (
<div className="grow">
<MarqueeEffect gap={24} reverse>
{images.map((src, i) => (
<img
key={i}
src={src}
alt={`Product ${i + 1}`}
className="w-32 aspect-square rounded-md"
/>
))}
</MarqueeEffect>
</div>
);
}
Vertical














import { MarqueeEffect } from "@/components/core/marquee-effect";
const images = Array.from({ length: 7 }, (_, i) =>
`https://bundui-images.netlify.app/products/0${i + 1}.jpeg`
);
export default function MarqueeEffectExample() {
return (
<div className="h-80">
<MarqueeEffect gap={24} direction="vertical">
{images.map((src, i) => (
<img
key={i}
src={src}
alt={`Product ${i + 1}`}
className="w-32 aspect-square rounded-md"
/>
))}
</MarqueeEffect>
</div>
);
}
Props
Prop | Type | Default |
---|---|---|
className? | string | - |
reverse? | boolean | - |
direction? | "horizontal" | "vertical" | "horizontal" |
speedOnHover? | number | - |
speed? | number | 100 |
gap? | number | 16 |
children | React.ReactNode | - |
Magnetic Button
A Magnetic Button is a UI component designed to enhance user interaction. When a user's cursor or touch input approaches the button, it subtly moves towards the user, simulating the effect of magnetic attraction.
Ripple Effect
Ripple effect is a modern animation component designed to enrich user interaction by creating a dynamic ripple effect when clicking on buttons or any HTML element. Built with Tailwind CSS and Motion.