A flexible scrolling layout for showcasing repeating content like logos, announcements, or testimonials.
import Marquee from "@/registry/components/marquee/marquee";
const MarqueePreview = () => {
const javaScriptLibAndFrameworks = ["Knockout", "React", "Angular", "Vue", "Svelte", "Solid", "Astro", "Marko", "Wiz", "Qwik"];
return (
<div className="w-full min-w-0 p-8">
<Marquee>
{javaScriptLibAndFrameworks.map(e => (
<Card key={e} name={e} />
))}
</Marquee>
<br />
<Marquee reverse>
{javaScriptLibAndFrameworks.map(e => (
<Card key={e} name={e} />
))}
</Marquee>
</div>
)
};
const Card = (props) => {
const {
name
} = props;
return (
<div className="p-5 bg-neutral-200 dark:bg-neutral-600 text-neutral-900 dark:text-white rounded-lg">
{name}
</div>
)
};
export default MarqueePreview; Install the following packages before using this component.
import { memo } from "react";
import cn from "@/utils/cn";
import styles from "./marquee.module.css";
export type MarqueeProps = {
children: React.PropsWithChildren,
axis?: "horizontal" | "vertical",
pauseOnHover?: boolean,
reverse?: boolean;
duration?: number;
repeat?: number;
mask?: boolean;
className?: string;
} & React.ComponentProps<"div">;
const Marquee = (
props: MarqueeProps
) => {
const {
children,
axis = "horizontal",
pauseOnHover = true,
reverse = false,
duration = 30,
repeat = 5,
mask = true,
className,
style,
...restProps
} = props;
const _repeat = Math.max(1, repeat);
const _duration = Math.max(1, duration);
return (
<div
{...restProps}
className={cn(
"overflow-hidden",
className,
{
"[mask-image:linear-gradient(to_right,transparent,white_var(--mask-breakpoint),white_calc(100%-var(--mask-breakpoint)),transparent)]": mask && axis === "horizontal",
"[mask-image:linear-gradient(to_bottom,transparent,white_var(--mask-breakpoint),white_calc(100%-var(--mask-breakpoint)),transparent)]": mask && axis === "vertical",
}
)}
style={{
"--gap": "16px",
"--mask-breakpoint": "20%",
...style,
"--animation-duration": `${_duration}s`,
} as React.CSSProperties}
>
<div
className={cn(
"group flex w-max-content [gap:var(--gap)]",
{
"[flex-direction:row]": axis === "horizontal",
"[flex-direction:column]": axis === "vertical",
},
)}
>
{Array.from({
length: _repeat,
}).map((_, index) => (
<div
key={`marquee-block-${index}`}
aria-hidden={index !== 0}
className={cn(
"flex",
"[gap:var(--gap)]",
"[animation-timing-function:linear]",
"[animation-iteration-count:infinite]",
"[animation-duration:var(--animation-duration)]",
"group-hover:[animation-play-state:paused]",
{
"[flex-direction:row]": axis === "horizontal",
"[flex-direction:column]": axis === "vertical",
"[animation-direction:reverse]": reverse,
}
)}
style={{
animationName: styles[`marquee-list-${axis}-keyframes`],
}}
>
{children}
</div>
))}
</div>
</div>
);
};
export default memo(Marquee); @keyframes marquee-list-horizontal-keyframes {
from {
transform: translateX(0);
}
to {
transform: translateX(calc(-100% - var(--gap)));
}
}
@keyframes marquee-list-vertical-keyframes {
from {
transform: translateY(0);
}
to {
transform: translateY(calc(-100% - var(--gap)));
}
} | Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| children | React.ReactNode | Yes | — | Items to be displayed inside the marquee. These elements will scroll continuously. |
| axis | "horizontal" | "vertical" | No | "horizontal" | Controls the scrolling direction of the marquee. |
| pauseOnHover | boolean | No | true | Pauses the marquee animation when the user hovers over it. |
| reverse | boolean | No | false | Reverses the scrolling direction of the marquee animation. |
| duration | number | No | 30 | Duration of one animation cycle in seconds. Minimum value is 1. |
| repeat | number | No | 5 | Number of times the marquee content is repeated to maintain continuous scrolling. Increase this if the marquee items are small. |
| mask | boolean | No | true | Applies a fade mask at the beginning and end of the marquee. |
| className | string | No | — | Additional CSS class names applied to the marquee container. |
| style | React.CSSProperties | No | — | Inline styles applied to the marquee container. |
If you find this component useful, consider starring the repository on GitHub. Found a bug or have a suggestion? Open an issue to help improve it.