Marquee
A band of words that drifts through a clipped frame. Scroll takes over; a small pause word lets it rest.
Report an issue with MarqueeExample
- Silence
- Space
- Type
- Rest
- Line
- Measure
- New era
- Of silence
- Oslo 2026
- Set in Archivo
- Printed on paper
- Read slowly
- Spectra
- Point
- Dust
- Hush
Autoplaying
- Silence
- Space
- Type
- Rest
- Line
- Measure
Paused
- Silence
- Space
- Type
- Rest
- Line
- Measure
Reduced motion
- Silence
- Space
- Type
- Rest
- Line
- Measure
Scroll only
- Silence
- Space
- Type
- Rest
- Line
- Measure
Reverse
- Silence
- Space
- Type
- Rest
- Line
- Measure
Ticker
- Plate 04
- Oslo
- 2026
- Archivo
Counter
- Silence
- Space
- Type
- Rest
- Line
- Measure
Right to left
- صمت
- مساحة
- خط
- راحة
Install
Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base along.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/marquee.jsonUsage
import * as React from "react"import { Marquee } from "@/components/ui/marquee"import { State, Still } from "@/components/site/state"const words = ["Silence", "Space", "Type", "Rest", "Line", "Measure"]export default function Example() {const still = React.useContext(Still)return (<div className="grid gap-y-16"><Marquee autoplay={!still} className="ot-fff" label="What the page is made of">{words.map((w) => <span key={w}>{w}</span>)}</Marquee><Marquee autoplay={!still} variant="ticker" label="Index">{["New era", "Of silence", "Oslo 2026", "Set in Archivo", "Printed on paper", "Read slowly"].map((w) => <span key={w}>{w}</span>)}</Marquee><Marquee autoplay={!still} variant="counter" className="ot-fff" label="Both ways">{["Spectra", "Point", "Dust", "Hush"].map((w) => <span key={w}>{w}</span>)}</Marquee></div>)}Contract
- Underneath: native, plus a hook that reads scroll and an elapsed-time animation clock.
- Anatomy:
<div class="ot-marquee" data-variant="ticker | counter">holding.ot-marquee-frame(overflow clip, a zero minimum inline size, inside aminmax(0, 1fr)grid column) and the.ot-marquee-pauseword button beneath it. The frame holds one.ot-marquee-row(two for counter;data-backon the one that runs towards the start), each a.ot-marquee-trackof.ot-marquee-wordslists. The first list of the first row is the one readers get (aria-labelfromlabel); the copies that fill the band are aria-hidden and inert.--ois how far it has travelled, wrapped to one run (--period). - A band of words that drifts at 24px per second by default. Scrolling takes over immediately: its position is the accumulated drift plus how far the band has risen through the view times
speed(0.4), read after a smooth scroller's frame. It runs back when you scroll back and resumes drifting after a short rest. Scrolling down it reads along, the words coming in at the end and leaving at the start;reverseturns both drift and scroll round. The duplicated track is clipped by its own frame and can never set the page's width. - band (the default): after "Renaissance." and "Less is more.", every word a statement closed by its own full stop, and after SPECTRA, set so large in a light weight that the frame's ends crop it. Pass a dynamic class (
ot-fff,ot-ff) for the size. - ticker: small capitals, widely spaced (
--ot-space-7), in graphite between two hairlines, after the Renaissance poster's top row and the SHAPES / GRADIENTS frame row. No full stops: these are labels, not statements. - counter: after SPECTRA's two rows. The band twice, the upper row cut at its head and the lower at its feet, meeting on one hairline and running opposite ways during drift and scroll.
- It measures again when its width or one run's width changes (a new face or size), so it needs no watch on
<html>. Right to left the words read and travel the other way. Reduced motion: the words stand still and wrap as one list, the copies and the second row gone. - Autoplay (owner-approved exception, 2026-10-01):
autoplaydefaults to true; false keeps only person-driven behaviour and omits the pause control.defaultPausedstarts it paused. The small roman( pause )/( play )button sits beneath the item at inline start, withpauseLabel/playLabelfor its visible and accessible words. Explicit pause persists until play. Hover, focus within, an off-screen IntersectionObserver entry and a hidden tab suspend it; the person's scroll, drag, key or press takes over, and leaving interaction gives it 1600ms of rest before resuming. Reduced motion disables autoplay entirely and hides the unused control;data-force="reduced"shows that still state in docs. The clock uses rAF time deltas, cancels on unmount and never catches up hidden time. - Autoplay states: autoplaying, paused (play available), reduced motion (still); the pause button is a separate keyboard stop with visible accent focus and native Enter/Space activation.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
autoplay | boolean | true | Play between interactions. Pause on hover, focus within, off-screen or in a hidden tab; resume after 1.6 seconds of rest. Always off under reduced motion. False keeps only person-driven changes and hides the pause control. |
defaultPaused | boolean | false | Start paused with a visible play control. The person can resume it. |
pauseLabel | string | "pause" | The visible word on the pause button, also its accessible name. Parentheses are drawn around it. |
playLabel | string | "play" | The visible word when explicitly paused. Hover and focus still hold autoplay after play is pressed. |
children | ReactNode | The words: each child is one word or phrase in the band. Readers get them once, as a list; the copies that fill the band are hidden from them. | |
variant | "band" | "ticker" | "counter" | "band" | band: after "Renaissance." and SPECTRA's crop, display words in a light weight, each closed by a full stop, cut off by the frame's ends. ticker: small capitals, widely spaced, running between two hairlines, after the Renaissance poster's top row. counter: after SPECTRA's two rows, the band twice, the upper row cut at its head and the lower at its feet, meeting on a hairline and running opposite ways. |
speed | number | 0.4 | How far the words travel for each pixel the page scrolls. |
reverse | boolean | false | Run towards the start as you scroll down instead of reading along. In counter it swaps which row does. |
label | string | Names the list of words for readers. | |
className | string | Pass a dynamic class (ot-fff, ot-ff…) for the band's size; the ticker keeps its own. |
Craft
Where its move comes from
| Component | Reference | Move |
|---|---|---|
| Marquee | "Renaissance."; SPECTRA crop | Huge light words, each closed by a full stop, cropped by the frame, drifting until the reader takes over |
| Ticker marquee | The Renaissance top row; SHAPES / GRADIENTS frame row | Small capitals running between two hairlines |
| Counter marquee | SPECTRA's two cropped rows | Two rows cut at head and feet, meeting on a hairline, running opposite ways |
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Marquee | Rubato (the reader's tempo) | A 24px/s drift yields to scroll; pausable, off under reduced motion (owner-approved 2026-10-01) |
| Counter marquee | Contrary motion | The two clipped rows drift or scroll opposite ways, sharing the same pause and reduced-motion rules |