Text on Path
NewFreeTypography flowing along curves: real SVG text that loops seamlessly along a wave, ring, spiral, infinity or any path, bends toward the pointer, follows the scroll and spins as a stamp, in five typographic moods.
- text
- typography
- svg
- textpath
- marquee
- path
- ring
- badge
- scroll
- pointer
Installation
pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/text-on-path.jsonNeeds a React + Tailwind project set up with shadcn. Copies the effect into your project, along with lib/effect-runtime.ts, the small WebGL layer it runs on. No other dependencies.
Or from npm
pnpm add @kagadmodyaa/effectsimport { TextOnPath } from '@kagadmodyaa/effects/text-on-path'One package with every free effect, one import path each. No Tailwind or shadcn needed, and no stylesheet to import: the few layout rules an effect needs come with it. The npm package and the command above ship the same source.
Usage
Use it like any other component. Choose a theme with preset; any prop you pass as well overrides the preset.
import { TextOnPath, TextRing } from '@/components/effects/text-on-path/text-on-path'
export function Hero() {
return (
<>
{/* The parent sets the height; the text fills it. background="transparent" lets your page show through. */}
<div className="h-[420px]">
<TextOnPath preset="Swiss" text="Form follows the curve" path="wave" speed={0.8} background="transparent" />
</div>
{/* Any shape: wave, circle, spiral, infinity, arc, or your own SVG path d string */}
<div className="h-[420px]">
<TextOnPath preset="Brass" path="custom" customPath="M10 200 C 120 20, 240 20, 300 160 S 480 300, 590 120" secondLine={false} />
</div>
{/* Follow the page scroll instead of the clock */}
<div className="h-[320px]">
<TextOnPath preset="Editorial" path="arc" scrollDriven />
</div>
{/* A rotating badge with your own icon in the middle */}
<TextRing preset="Neon Script" text="Scroll down" size={150}>
<ArrowDownIcon />
</TextRing>
</>
)
}
// Every look prop (text, path, speed, fontFamily, color, accent, glow, bend, ...) works on TextOnPath, TextRing and
// <TextOnPathShowcase />, which is the poster above. Extra props override the preset.
<TextOnPath preset="Neon Script" text={"Typography is the voice of the page"} />Presets
EditorialNeon ScriptSwissBrassMono Wire
Props
| Name | Type | Default | Description |
|---|---|---|---|
| preset | 'Editorial' | 'Neon Script' | 'Swiss' | 'Brass' | 'Mono Wire' | none (the defaults below) | A named theme. Any prop you pass as well overrides the preset’s value. |
| text | 'Typography is the voice of the page' | 'Letters learn to travel' | 'Form follows the curve' | 'Read between the lines' | 'Make every word move' | 'Typography is the voice of the page' | The words that flow along the path. Any string works as a prop. |
| secondLine | boolean | true | A second, smaller line on a parallel path in a contrasting style, drifting against the first. |
| secondText | 'Vol. 07 — Type specimen' | 'Set in motion' | 'Est. 2026' | 'Read slowly' | 'Aa Bb Cc 123' | 'Vol. 07 — Type specimen' | The words of the second line. |
| path | 'wave' | 'circle' | 'spiral' | 'infinity' | 'arc' | 'custom' | 'wave' | The curve the text follows. custom takes any SVG path d string through customPath. |
| showGuide | boolean | true | A hairline along the path. |
| guideNodes | boolean | false | Small dots at the path nodes; they ride the bend. |
| bend | number | 0.8 | How far the pointer pulls the path toward it before it springs back. 0 turns the bend off. Range 0 to 1.5. |
| speed | number | 0.6 | Marquee speed. 1 is about 90 px a second at the reference size. Range 0 to 2. |
| direction | 'forward' | 'reverse' | 'forward' | Which way the text travels along the path. |
| scrollDriven | boolean | false | The text follows the page scroll instead of the clock. |
| fontFamily | 'sans' | 'serif' | 'mono' | 'serif' | A serif, sans or monospace stack. The second line takes the opposite voice. |
| fontSize | number | 98 | Size in px on a 1000px wide stage; it scales with the width. Range 40 to 200 px. |
| weight | number | 400 | Font weight, 300 to 900. Range 300 to 900. |
| letterSpacing | number | -0.01 | Tracking in em. Closed loops adjust it slightly so the repeats meet exactly. Range -0.1 to 0.4 em. |
| italic | boolean | false | Slanted letters. |
| caps | boolean | false | Upper-case the first line. |
| color | string | '#1c1915' | The first line. A hex colour. |
| accent | string | '#c8452d' | The second line, the separators, node dots and the stamp. A hex colour. |
| background | string | '#f3ecde' | Fill behind the text. Use transparent to sit on your own page. A hex colour. |
| glow | number | 0 | A soft halo around the letters, drawn with an SVG filter. Range 0 to 2. |
| ringSize | number | 190 | Diameter of the rotating TextRing, in px at the reference size. Range 80 to 320 px. |
| ringText | 'Scroll down' | 'Now playing' | 'Handle with care' | 'Made to be read' | 'Est. 2026' | 'Scroll down' | The words around the TextRing. |
| className | string | — | Extra class on the root element. The effect fills its parent: give the parent a height. |
| style | CSSProperties | — | Inline style on the root element. |
Files
What the command adds to your project, and where.
components/effects/text-on-path/text-on-path.tsxThe component.
components/effects/text-on-path/path-text.tsxThe component.
components/effects/text-on-path/ring.tsxThe component.
components/effects/text-on-path/showcase.tsxThe component.
components/effects/text-on-path/looks.tsThe component.
components/effects/text-on-path/geometry.tsThe component.
components/effects/text-on-path/hooks.tsThe component.
components/effects/text-on-path/parts.tsxThe component.
components/effects/text-on-path/stage-styles.tsThe component.
lib/effect-runtime.tsShared by every shader effect: resizing, pausing, reduced motion, context loss and the no-WebGL fallback. Installed once.
Behaviour
- The words are real SVG text on a textPath: crisp at any size, selectable, and named for screen readers by an aria-label on the graphic (the repeats are not read out).
- The text repeats to fill the path, measured with getTotalLength, and slides along it by animating startOffset by exactly one repeat, so the loop has no seam. Closed shapes (circle, infinity) adjust the tracking a hair so a whole number of repeats fits.
- The pointer bends the path toward it with a smooth falloff; each control point is a spring, so the line leans in, overshoots a little and settles back. Hovering the stage eases the marquee down. bend={0} turns the bend off.
- scrollDriven ties the offset to the page scroll (scroll down and the text runs one way, scroll up and it runs back), smoothed so it never jumps.
- The second line sits on a parallel offset path in the opposite voice (serif becomes small sans capitals, sans becomes mono, mono becomes serif italic) and drifts against the first.
- TextRing sets words on a circle sized so a whole number of repeats closes it, spins it around a centre slot (an icon, a logo, an image) and slows on hover. filled gives a solid stamp.
- Under prefers-reduced-motion everything is a still frame: no marquee, no spin, no bend. paused={true} does the same on request.
- The effect renders deterministic markup on the server and measures after mount, so there is no hydration mismatch. It pauses off screen and in background tabs.