Text ribbon
A phrase travelling along an arch or a wave, measured by pretext. Drag, keys and scroll take over; pause holds it in place.
Report an issue with Text ribbonExample
a little room to move
Let the phrase drift, or take over with a drag, the keys or the page. Pause holds it still.
everything here is made of type
Autoplaying
one note of colour
Paused
a moment of rest
Reduced motion
the phrase stands still
Move it yourself
the hand sets the pace
Wave
silence is structure
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/text-ribbon.jsonUsage
import * as React from "react"import { TextRibbon } from "@/components/ui/text-ribbon"import { State, Still } from "@/components/site/state"export default function Example() {const still = React.useContext(Still)return (<div className="grid w-full grid-cols-[minmax(0,1fr)] gap-(--ot-space-8)"><div className="grid gap-(--ot-space-3)"><TextRibbon autoplay={!still} className="ot-mf">a little room to move</TextRibbon><p className="ot-pp" style={{ margin: 0, color: "var(--ot-pencil)" }}>Let the phrase drift, or take over with a drag, the keys or the page. Pause holds it still.</p></div><TextRibbon autoplay={!still} variant="wave" className="ot-mp">everything here is made of type</TextRibbon></div>)}Contract
- Underneath: native, plus a hook that measures every letter with pretext (
@chenglou/pretext, loaded when it's needed); the stage is arole="slider". - Anatomy:
<div class="ot-ribbon" data-variant="wave">holding the phrase once for readers (.ot-sr) and.ot-ribbon-stage(the slider, one focus stop), followed by the.ot-ribbon-pauseword button beneath it. The stage holds.ot-ribbon-plain(the phrase, until it's laid), an aria-hiddensvg.ot-ribbon-guideand an aria-hidden.ot-ribbon-letters: one span per letter and a.ot-ribbon-dotbetween each repeat, each placed by the hook and carrying--d, its distance from the middle of the curve, 0 to 1. - arc (the default): after WOVE, figures set round an arc with dots on it, fading and shrinking by distance. The phrase is repeated round an arch that spans 100°, each letter at its place in the real line (kerning included) and turned to the curve. At the crest it stands at full size in ink; toward the ends it shrinks (to 58%) and falls to pencil, and it fades out at the very ends. A hairline guide (
--ot-rule) runs 0.55em inside the letters, carrying a pencil dot between each repeat and one ink dot fixed at the crest, the "here" of WOVE's dial. The stage's height is reserved from its width in CSS (container units), so nothing moves when it's laid. - The phrase drifts at 0.6em per second. Drag it along (sideways; on touch the page still scrolls up and down), use the keys, or scroll the page to take over. Scroll carries it 0.6 of the page's movement, read from the real layout after a smooth scroller (Lenis) has moved it. Person-driven changes have no inertia; autoplay resumes after a short rest.
- wave: after the script that sweeps through the capitals of "Less stress. More creativity.". The phrase rides a slow wave across the measure (0.6em high, a swell every 12em or more), every letter the same size and in ink, fading in and out only at the edges; the dots ride a guide under it.
- Keyboard: Left and Right move it an em, Page Up and Page Down four, Home back to where it began.
aria-valuenowis how far round the repeat it has gone, 0 to 100. Focus draws the accent outline and turns the crest dot to the accent. The letters are aria-hidden and the phrase is read once. - Under reduced motion neither autoplay nor scroll carries it: it shows the still curve; the drag and the keys still work. It lays out again when its width changes or
<html>changes face (it watches the pair, scheme, mode and key attributes, notclass, which smooth scrolling toggles on every scroll). Before that, or without script, it's the plain phrase. The letters are placed left to right one by one, so it is for Latin phrases; the drag and the arrows follow the screen, not the page's direction. Forced colours: the letters and dots in CanvasText, the guide in GrayText. - 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 | string | The phrase, as plain text: pretext measures every letter, kerning included. It is read once; the repeats are for the eye. | |
variant | "arc" | "wave" | "arc" | arc: after WOVE's figures on an arc; the phrase round an arch, in ink and at full size at the crest, where one ink dot marks the middle of the guide, shrinking and falling to pencil toward the ends. wave: the phrase rides a slow wave across the measure, every letter the same size, fading in and out at the edges. |
label | string | "Move the phrase" | The name of the slider you move it by. |
Craft
Where its move comes from
| Component | Reference | Move |
|---|---|---|
| Text ribbon | WOVE (figures along an arc); the fermata arc | The phrase round an arch, largest at the crest and fading by distance |
| Wave text ribbon | "Less stress. More creativity." sweeping script | The phrase rides a slow wave across the measure |
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Text ribbon | Carried | A 0.6em/s drift yields to drag, keys and scroll; pausable, off under reduced motion (owner-approved 2026-10-01) |