Short captions that step in under a scene, one line at a time, with Text Swap's motion. It runs on its own clock or follows a scene's timeline (eased-demo-core).
When to use
- Narrating a product demo: one short line per step, under the stage.
- A hero or empty state that cycles through what a product does, each line on its own timing.
- Subtitles for a looping illustration.
When not to use
Installation
npx shadcn add https://eased.dev/r/caption-ticker.jsonThe command adds Caption Ticker, everything it builds on and Eased’s tokens to your project.
Usage
import { CaptionTicker } from "@/components/eased/caption-ticker/caption-ticker";export function Example() { return ( <CaptionTicker captions={[ { text: "Connect your repository", duration: 2000 }, { text: "Push to main", duration: 1600 }, { text: "Share the preview link", duration: 2000 }, ]} /> );}Examples
Driven by a scene
Pass index and the ticker shows that line; the scene's useDemoTimeline decides when.
<CaptionTicker captions={["Press Deploy", "Fathom builds the commit", "Live at fathom.app"]} index={step === "build" ? 1 : step === "live" ? 2 : 0} />Following along
onIndexChange reports each line on the ticker's own clock, so what it describes can light with it; paused holds it from a Pause control.
<CaptionTicker captions={CAPTIONS} paused={paused} onIndexChange={setCurrent} />API reference
CaptionTicker
| Prop | Type | Default | Description |
|---|---|---|---|
captions | (string | { text: string; duration?: number })[] | The lines. duration is how long a line stays up on its own clock, in ms. | |
index | number | null | Show this line (driven by a scene). null shows none. Leave it out to run on its own clock. | |
loop | boolean | true | On its own clock, start again after the last line. |
paused | boolean | false | Hold the current line on its own clock. |
duration | number | 2400 | How long a line without a duration stays up, in ms. |
onIndexChange | (index: number) => void | Called as its own clock moves to a line. | |
className | string | "" | Appended to the root's classes. |
Accessibility
- The moving line is
aria-hidden; screen readers get every caption at once as a list, so a loop never repeats at them. - Nothing is focusable. If the captions narrate an autoplaying scene, the scene's Pause control holds both.
Motion
Each new line rises 4px from below with a 4px blur on the snappy spring while the old one lifts 3px and blurs out on a faster tween, and the box springs its width to the new line. On its own clock the ticker schedules each line from when the last was due, so it never drifts; it pauses off-screen and in a hidden tab. Under reduced motion lines cross-fade in 100ms, and its own clock rests on the last line.
Notes for AI
Use Caption Ticker under a demo stage to narrate it, with index derived from the same useDemoTimeline step as everything else, or on its own with a duration per line. Keep every line to a few words.
Do not drive it with your own setInterval, and do not put it in a live region. It already pauses off-screen, swaps without layout jumps and exposes all lines to screen readers.
Related
- Text Swap: the motion it uses, for any changing label.
- Demo Cursor: what it usually narrates.
- Product demo set: the pieces on one timeline.