Marquee

A strip that loops its items only once CSS scroll-driven animations detect they overflow

Installation

pnpm dlx shadcn@latest add @alexandrejoly/marquee

Usage

import {  Marquee,  MarqueeContent,  MarqueeItem,  MarqueePause,  MarqueeViewport,} from "@/components/ui/marquee";
<Marquee>  <MarqueePause />  <MarqueeViewport>    <MarqueeContent>      <MarqueeItem>Item 1</MarqueeItem>      <MarqueeItem>Item 2</MarqueeItem>    </MarqueeContent>  </MarqueeViewport></Marquee>

Examples

Pause on hover

A slower track, from [--marquee-duration:60s], pauses under the pointer so longer items can be read.

A longer item to read, number 1
A longer item to read, number 2
A longer item to read, number 3
A longer item to read, number 4

Reverse

Two tracks in one marquee, the second with reverse, pass each other.

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16

Vertical

A vertical track, faded at the top and bottom, runs on a shorter cycle from [--marquee-duration:12s].

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6

Content that fits

Items narrower than the viewport stand still: no clone, no fade, no pause button. CSS scroll-driven animations detect the overflow.

Item 1
Item 2

Centered when it fits

items-center-safe on MarqueeViewport centers a track that fits. Once the track overflows, safe aligns it to the start again, so the loop runs as usual.

Item 1
Item 2

Accessibility

Screen readers and the Tab key skip the second copy of each track, so they meet every item once. A pointer can still click it.

MarqueePause lets people stop the motion: it stops every track where it is, and its name switches between Pause and Play. Place it before MarqueeViewport so Tab reaches it first.

Keyboard focus stops the tracks and brings the focused item fully into view. A pointer press, or hovering with pauseOnHover, pauses them only while it lasts. Neither replaces MarqueePause.

Under prefers-reduced-motion: reduce the tracks stand still and MarqueePause hides.

Meets WCAG 2.2: 2.2.2 Pause, Stop, Hide (opens in a new tab) · 2.4.3 Focus Order (opens in a new tab) · 2.4.11 Focus Not Obscured (Minimum) (opens in a new tab) · 4.1.2 Name, Role, Value (opens in a new tab)

API reference

Marquee

Holds the pause state and lines up MarqueePause beside the viewport.

Takes every prop of div.

PropTypeDefault
defaultPaused

The pause state on first render, when you don’t control it.

Default: false

booleanfalse
paused

The pause state, when you control it.

booleanNone
onPausedChange

Called when MarqueePause is pressed.

(paused: boolean) => voidNone

MarqueeViewport

Clips the tracks. While its items fit, it stands still with no clone, in browsers with CSS scroll-driven animations. Add shadcn’s scroll-fade-x, or scroll-fade-y on a vertical track, to fade its edges while the items slide: it sets --scroll-fade-reveal: 0px, so both fades show although it never scrolls. Its scroll-padding: 50% centers the item keyboard focus lands on, so keep it. It clips on both axes, since detecting the overflow needs a scroll container, so it cuts the focus ring of a link inside: give the ring room with py-1, or px-1 around a vertical track.

Takes every prop of div.

MarqueeContent

The track. It renders its child elements twice and a CSS keyframe slides it by one copy per cycle. Bare text is dropped, so wrap it in MarqueeItem. Under reduced motion it stays on the first copy.

Takes every prop of div.

Prop or variableTypeDefault
orientation

vertical stacks the items and slides them upward.

Default: "horizontal"

"horizontal" | "vertical""horizontal"
reverse

Slides the other way. Horizontal only.

Default: false

booleanfalse
pauseOnHover

Pauses the track while a pointer is over the viewport.

Default: false

booleanfalse
--marquee-duration

One full cycle, set from a class: [--marquee-duration:20s].

Default: 40s

CSS time40s

MarqueeItem

One item of the track. Its padding spaces the items and the loop’s seam alike: px-4 on a horizontal track, py-2 on a vertical one. Override it with a class such as px-2.

Takes every prop of div.

MarqueePause

Pauses and resumes every track. It is a ghost icon button by default; pass variant or size to change it. It hides whenever the tracks stand still: under reduced motion, and while the items fit in browsers with CSS scroll-driven animations.

Takes every prop of shadcn Button (opens in a new tab).

View as Markdown