Marquee
A zero-dependency, ultra-lightweight marquee. Uses pure CSS for lightning-fast infinite scrolling.
Horizontal Scroll (Pause on Hover)
Item 1
Bare UI is native.
Item 2
Bare UI is native.
Item 3
Bare UI is native.
Item 4
Bare UI is native.
Item 5
Bare UI is native.
Item 6
Bare UI is native.
Item 1
Bare UI is native.
Item 2
Bare UI is native.
Item 3
Bare UI is native.
Item 4
Bare UI is native.
Item 5
Bare UI is native.
Item 6
Bare UI is native.
Reverse Direction
React
Next.js
Pure CSS
Zero Deps
Performance
Accessible
React
Next.js
Pure CSS
Zero Deps
Performance
Accessible
Installation
1. Add the CSS to your global stylesheet:
css
/* Bare Marquee Animation */
.bare-marquee-wrapper {
overflow: hidden;
display: flex;
position: relative;
width: 100%;
mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
-webkit-mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
}
.bare-marquee-wrapper[style*="--marquee-direction"] .bare-marquee.vertical {
mask-image: linear-gradient(to bottom, transparent, black 10%, black 90%, transparent);
-webkit-mask-image: linear-gradient(to bottom, transparent, black 10%, black 90%, transparent);
height: 100%;
}
.bare-marquee {
display: flex;
animation: bare-marquee-scroll var(--marquee-speed, 20s) linear infinite var(--marquee-direction, normal);
}
.bare-marquee.horizontal {
width: max-content;
flex-direction: row;
}
.bare-marquee.vertical {
height: max-content;
flex-direction: column;
animation-name: bare-marquee-scroll-vertical;
}
.bare-marquee.pause-on-hover:hover {
animation-play-state: paused;
}
.bare-marquee-content {
display: flex;
flex-shrink: 0;
gap: var(--marquee-gap, 1rem);
padding-right: var(--marquee-gap, 1rem);
}
.bare-marquee.vertical .bare-marquee-content {
flex-direction: column;
padding-right: 0;
padding-bottom: var(--marquee-gap, 1rem);
}
@keyframes bare-marquee-scroll {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
@keyframes bare-marquee-scroll-vertical {
0% { transform: translateY(0); }
100% { transform: translateY(-50%); }
}2. Copy the React component into your project:
tsx
import * as React from "react";
import { HTMLAttributes } from "react";
export interface MarqueeProps extends HTMLAttributes<HTMLDivElement> {
/**
* The speed of the marquee animation. Accepts standard CSS time values (e.g., '10s', '500ms').
* @default '20s'
*/
speed?: string;
/**
* Pause the animation when hovering over the marquee.
* @default false
*/
pauseOnHover?: boolean;
/**
* Direction of the marquee.
* @default 'normal'
*/
direction?: "normal" | "reverse";
/**
* Orientation of the marquee.
* @default 'horizontal'
*/
orientation?: "horizontal" | "vertical";
/**
* Gap between items. Accepts standard CSS length values.
* @default '1rem'
*/
gap?: string;
/**
* Content to scroll within the marquee.
*/
children: React.ReactNode;
}
/**
* A highly performant, zero-dependency Marquee component.
* Uses pure CSS animations to provide infinite scrolling with minimal bundle footprint.
*/
export const Marquee = React.forwardRef<HTMLDivElement, MarqueeProps>(
(
{
speed = "20s",
pauseOnHover = false,
direction = "normal",
orientation = "horizontal",
gap = "1rem",
children,
className = "",
style,
...props
},
ref
) => {
// Determine dynamic classes based on props
const orientationClass = orientation === "vertical" ? "vertical" : "horizontal";
const hoverClass = pauseOnHover ? "pause-on-hover" : "";
const classes = `bare-marquee ${orientationClass} ${hoverClass}`.trim();
// Inline CSS Variables to pass configuration cleanly to pure CSS
const customStyle = {
"--marquee-speed": speed,
"--marquee-direction": direction,
"--marquee-gap": gap,
...style,
} as React.CSSProperties;
return (
<div
ref={ref}
className={`bare-marquee-wrapper ${className}`}
style={customStyle}
{...props}
>
<div className={classes}>
<div className="bare-marquee-content">{children}</div>
{/* Duplicate content to create the infinite scroll effect seamlessly */}
<div className="bare-marquee-content" aria-hidden="true">
{children}
</div>
</div>
</div>
);
}
);
Marquee.displayName = "Marquee";
Usage
tsx
import { Marquee } from '@/components/ui/marquee';
import { GlassCard } from '@/components/ui/glass-card';
export default function MyComponent() {
return (
<Marquee speed="25s" pauseOnHover>
{[1, 2, 3, 4].map((i) => (
<GlassCard key={i} style={{ width: '200px', padding: '1rem' }}>
Item {i}
</GlassCard>
))}
</Marquee>
);
}