Skip to content

Marquee

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.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

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.

NorthwindHarbor LabsLumenFieldnoteOrchardTessera
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 marquee

Copies 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.json

Works, 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

Setup took ten minutes.Our forms finally pass audits.Support answered the same day.The keyboard flow just works.
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

DraftReviewApprovePublishArchive
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.

PropTypeDefault
childrenrequired

The items that scroll.

nodenone
direction

Axis the content scrolls along. A vertical marquee needs a parent or className that gives it a height.

"horizontal" | "vertical""horizontal"
reverse

Scrolls the other way.

booleanfalse
speed

Scroll speed in pixels per second.

number40
gap

Space between items and between repeats, any CSS length.

string"1rem"
pauseOnHover

Pauses while the pointer is over it or focus is inside it.

booleantrue
showPauseButton

Shows a visible Pause and Play button, so people can stop the motion (WCAG 2.2.2).

booleanfalse
pauseLabel

Text of the button while moving.

string"Pause"
playLabel

Text of the button while paused.

string"Play"
fade

Fades the content out at both edges.

booleantrue
isPaused

Stops the scrolling.

booleanfalse

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