II Type

Star on GitHubFeedback
II TypeNative HTML and a hookot-marquee

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 Marquee

Example

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

Usage

marquee-example.tsx
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 a minmax(0, 1fr) grid column) and the .ot-marquee-pause word button beneath it. The frame holds one .ot-marquee-row (two for counter; data-back on the one that runs towards the start), each a .ot-marquee-track of .ot-marquee-words lists. The first list of the first row is the one readers get (aria-label from label); the copies that fill the band are aria-hidden and inert. --o is 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; reverse turns 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): autoplay defaults to true; false keeps only person-driven behaviour and omits the pause control. defaultPaused starts it paused. The small roman ( pause ) / ( play ) button sits beneath the item at inline start, with pauseLabel / playLabel for 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

Props of Marquee
PropTypeDefaultWhat it does
autoplaybooleantruePlay 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.
defaultPausedbooleanfalseStart paused with a visible play control. The person can resume it.
pauseLabelstring"pause"The visible word on the pause button, also its accessible name. Parentheses are drawn around it.
playLabelstring"play"The visible word when explicitly paused. Hover and focus still hold autoplay after play is pressed.
childrenReactNodeThe 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.
speednumber0.4How far the words travel for each pixel the page scrolls.
reversebooleanfalseRun towards the start as you scroll down instead of reading along. In counter it swaps which row does.
labelstringNames the list of words for readers.
classNamestringPass a dynamic class (ot-fff, ot-ff…) for the band's size; the ticker keeps its own.

Craft

Where its move comes from

Where its move comes from
ComponentReferenceMove
Marquee"Renaissance."; SPECTRA cropHuge light words, each closed by a full stop, cropped by the frame, drifting until the reader takes over
Ticker marqueeThe Renaissance top row; SHAPES / GRADIENTS frame rowSmall capitals running between two hairlines
Counter marqueeSPECTRA's two cropped rowsTwo rows cut at head and feet, meeting on a hairline, running opposite ways

How it moves

How it moves
ComponentArticulationWhat moves
MarqueeRubato (the reader's tempo)A 24px/s drift yields to scroll; pausable, off under reduced motion (owner-approved 2026-10-01)
Counter marqueeContrary motionThe two clipped rows drift or scroll opposite ways, sharing the same pause and reduced-motion rules