A flexible, performant, and accessible marquee (scrolling text/content) component built with React and TypeScript.
npm install rauler
yarn add rauler
pnpm add rauler
import { Marquee } from "rauler" function App() { return ( <Marquee.Root> <Marquee.Content> {items.map((item) => ( <Marquee.Item key={item.id}>{item.content}</Marquee.Item> ))} </Marquee.Content> </Marquee.Root> ) }
| Prop | Type | Default | Description |
|---|---|---|---|
| speed | number | 50 | Scrolling speed in pixels per second |
| direction | 'rtl' | 'ltr' | 'ltr' | Scroll direction |
| pauseOnHover | boolean | false | Whether to pause on hover |
| isPaused | boolean | false | Controlled pause state |
| className | string | undefined | Optional CSS class |
| children | ReactNode | undefined | Marquee content |
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | undefined | Optional CSS class |
| children | ReactNode | undefined | Marquee items |
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Whether to merge props onto child element |
| className | string | undefined | Optional CSS class |
| children | ReactNode | undefined | Item content |
| Prop | Type | Default | Description |
|---|---|---|---|
| side | 'start' | 'end' | required | Which side to render the gradient |
| className | string | undefined | Optional CSS class |
A custom React hook that provides smooth, infinite scrolling animation functionality with pause/resume capabilities and reduced motion support.
| Parameter | Type | Default | Description |
|---|---|---|---|
| speed | number | 50 | Scrolling speed in pixels per second |
| direction | 'ltr' | 'rtl' | 'ltr' | Scroll direction |
| isPaused | boolean | false | Whether the animation is paused |
| children | ReactNode | undefined | Content to be animated |
| Property | Type | Description |
|---|---|---|
| containerRef | React.RefObject | Ref for the container element |
| contentRef | React.RefObject | Ref for the content element |
| loopCopies | number[] | Array of indices for content copies |
| translate | number | Current translation value |
| contentWidth | number | Width of the content |
| prefersReducedMotion | boolean | User's motion preference |
function MarqueeComponent() { const { containerRef, contentRef, loopCopies, translate, contentWidth, prefersReducedMotion, } = useMarquee({ speed: 50, direction: "ltr", isPaused: false, }) return ( <div ref={containerRef} style={{ overflow: "hidden" }}> <div ref={contentRef} style={{ transform: `translateX(${translate}px)`, display: "flex", width: "max-content", }} > {/* Your content */} {loopCopies.map((i) => ( <div key={i}>{/* Your content (duplicated) */}</div> ))} </div> </div> ) }
function MarqueeWithPause() { const [isPaused, setPaused] = useState(false) const { containerRef, contentRef, loopCopies, translate } = useMarquee({ speed: 50, isPaused, }) return ( <div ref={containerRef} onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)} > <div ref={contentRef} style={{ transform: `translateX(${translate}px)`, display: "flex", width: "max-content", }} > {/* Your content */} {loopCopies.map((i) => ( <div key={i}>{/* Your content (duplicated) */}</div> ))} </div> </div> ) }
<Marquee.Root direction="rtl" pauseOnHover> <Marquee.Content> {items.map((item) => ( <Marquee.Item asChild key={item.id}> <button onClick={() => console.log(item.id)}>{item.content}</button> </Marquee.Item> ))} </Marquee.Content> </Marquee.Root>
<Marquee.Root speed={100}> <Marquee.Gradient side="start" className="w-24 bg-gradient-to-r from-background to-transparent" /> <Marquee.Content> {items.map((item) => ( <Marquee.Item key={item.id}>{item.content}</Marquee.Item> ))} </Marquee.Content> <Marquee.Gradient side="end" className="w-24 bg-gradient-to-l from-background to-transparent" /> </Marquee.Root>
Content Width
Ensure your content items have a fixed or determinable width for proper animation calculations.
Container Overflow
The root container should have overflow: hidden (applied automatically).
Direction Changes
The component handles direction changes smoothly, but avoid frequent changes for better performance.
Gradient Positioning
When using gradients, ensure they're positioned relative to the root container.
Using asChild
When using asChild, ensure the child element can accept and properly handle the props being passed down.
Contributions are welcome! Please read our contributing guidelines and code of conduct before submitting pull requests.
MIT © Rauler