Skip to content
WANCRAFT

C.101Typography & Text

Scroll Text Path

Type that travels along a curved SVG path — wave, arc, loop or your own — steered by scroll progress.

NewReactMotionSVGTailwindintermediate

Overview

Scroll Text Path sets a phrase on an SVG textPath and maps its startOffset to scroll progress, so the line glides along a wave, an arch, a loop-de-loop or any path data you pass. It tracks the window or any scrollable container, repeats the phrase to fill the path (measured after fonts load, not guessed), and can run with or against the path. Offsets are written straight to the DOM from motion's scroll values, so scrolling never re-renders React. The sentence is exposed to screen readers once, and reduced motion places it statically.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/scroll-text-path.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/scroll-text-path.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/scroll-text-path.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { ScrollTextPath } from "@/components/ui/scroll-text-path";

export function ScrollTextPathExample() {
  return (
    <section className="py-[40vh]">
      <ScrollTextPath />
    </section>
  );
}

03Props

PropTypeDefaultDescription
textstring"Scroll to steer the type"Phrase set along the path; exposed once to assistive technology.
path"wave" | "arc" | "loop""wave"Built-in path shape.
dstring—Custom SVG path data (overrides path), in viewBox coordinates.
viewBoxstring"0 0 1000 360"viewBox for a custom d.
containerRefObject<HTMLElement | null>—Scrollable ancestor to track (give it a non-static position). Defaults to the window.
offsetuseScroll offset["start end", "end start"]When the travel starts and ends, as in motion's useScroll.
repeatbooleantrueRepeat the phrase so it fills the path; otherwise one phrase crosses it.
separatorstring" • "Inserted between repetitions.
direction"forward" | "reverse""forward"Scrolling down moves the text along or against the path.
speednumber1Travel multiplier per unit of scroll.
fontSizenumber44Font size in viewBox units; the type scales with the element's width.
accentMode"separator" | "text" | "alternate""separator"Which part of the line takes the accent colour.
guidebooleantrueDraw the path as a dotted hairline.
smoothbooleantrueSmooth scroll progress with a light spring.
accentstring"#FF7448"Accent colour; deepened automatically on light backgrounds.
as"div" | "p" | "h2" | "h3""div"Root element.
textClassNamestring"font-semibold tracking-[-0.02em]"Classes for the SVG text.

04Features

  • 01Wave, arc and loop presets, or any custom path data
  • 02startOffset mapped from motion's useScroll — window or a scroll container
  • 03Repeats to fill the path using real measured lengths, re-measured when fonts load
  • 04Forward or reverse travel with a speed multiplier and optional spring smoothing
  • 05Accent on separators, the text, or every other phrase
  • 06Scroll updates write a DOM attribute directly — no React re-renders while scrolling
motion · scroll-linkedinput · scroll

05Accessibility

  • The phrase is exposed once as visually hidden text; the SVG with its repeated copies is aria-hidden.
  • With prefers-reduced-motion the text is placed statically on the path and does not follow scroll.
  • The accent is mixed towards ink on light backgrounds so accented words keep their contrast.

06Limitations

  • Very long phrases on short paths are capped at 60 repetitions.
  • A custom container must have a non-static position for useScroll to measure offsets.