An endless scrolling row (or column) of content, such as logos or short quotes. It pauses on hover and focus, and can show a visible Pause button.
Optional, and kept apart.
Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.
import { Marquee } from "@rdloom/react";
const names = ["Northwind", "Harbor Labs", "Lumen", "Fieldnote", "Orchard", "Tessera"];
export default function MarqueeBasicExample() {
return (
<div className="flex w-full justify-center">
<Marquee className="w-full">
{names.map((name) => (
<span key={name} className="rounded-full border border-[var(--rd-color-border-default)] px-4 py-1.5 text-sm">
{name}
</span>
))}
</Marquee>
</div>
);
}Installation
npx rdloom add marqueeCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.
It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/marquee.jsonWorks, but without upgrade tracking, and you import the motion CSS yourself.
Usage
import { Marquee } from "@rdloom/react";
<div className="flex w-full justify-center">
<Marquee className="w-full">
{names.map((name) => (
<span key={name} className="rounded-full border border-[var(--rd-color-border-default)] px-4 py-1.5 text-sm">
{name}
</span>
))}
</Marquee>
</div>With pause button
import { Marquee } from "@rdloom/react";
const quotes = ["Setup took ten minutes.", "Our forms finally pass audits.", "Support answered the same day.", "The keyboard flow just works."];
export default function MarqueeWithPauseButtonExample() {
return (
<div className="flex w-full justify-center">
<Marquee showPauseButton reverse speed={30} className="w-full">
{quotes.map((quote) => (
<span key={quote} className="whitespace-nowrap rounded-lg bg-[var(--rd-color-surface-subtle)] px-4 py-2 text-sm">
{quote}
</span>
))}
</Marquee>
</div>
);
}Vertical
import { Marquee } from "@rdloom/react";
const steps = ["Draft", "Review", "Approve", "Publish", "Archive"];
export default function MarqueeVerticalExample() {
return (
<div className="flex w-full justify-center">
<Marquee direction="vertical" speed={24} className="h-44 w-48">
{steps.map((step) => (
<span key={step} className="rounded-lg border border-[var(--rd-color-border-default)] px-4 py-2 text-center text-sm">
{step}
</span>
))}
</Marquee>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
childrenrequiredThe items that scroll. | node | none |
directionAxis the content scrolls along. A vertical marquee needs a parent or className that gives it a height. | "horizontal" | "vertical" | "horizontal" |
reverseScrolls the other way. | boolean | false |
speedScroll speed in pixels per second. | number | 40 |
gapSpace between items and between repeats, any CSS length. | string | "1rem" |
pauseOnHoverPauses while the pointer is over it or focus is inside it. | boolean | true |
showPauseButtonShows a visible Pause and Play button, so people can stop the motion (WCAG 2.2.2). | boolean | false |
pauseLabelText of the button while moving. | string | "Pause" |
playLabelText of the button while paused. | string | "Play" |
fadeFades the content out at both edges. | boolean | true |
isPausedStops the scrolling. | boolean | false |
Accessibility
Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Tab: moves to the Pause button and to anything focusable in the items
- Enter or Space: pauses or resumes when the button is focused
Screen readers announce
- The items are read once, in order, as ordinary content
What your code must do
- Moving content longer than five seconds can be paused: by the Pause button, by hover and focus-within, and by isPaused (WCAG 2.2.2)
- Only the first copy of the items is in the accessibility tree; the repeats are aria-hidden and inert, so nothing is read or focused twice
- The items are real DOM in the server HTML and are readable with no JavaScript
- Visitors who ask for less motion (prefers-reduced-motion), and anything inside rdm-still, get a still row that can be scrolled by hand, with the repeats removed
- In forced-colors (high contrast) mode the edge fade is removed
- Scrolls the right way in right-to-left layouts
Guidelines
Use it when
- A row of customer logos
- Short, non-essential quotes or tags
Avoid it when
- Content people must read closely or act on, since moving targets are hard to use
- Navigation
Don't
- Putting essential links in a moving row without a Pause button
- Very fast speeds
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-text-default--rd-color-border-default--rd-color-surface-default--rd-radius-control